Designing Custom Controls with PyQt

Материал из Wiki.crossplatform.ru

(Различия между версиями)
Перейти к: навигация, поиск
(удалил лишний шаблон, перенес категорию в конец статьи)
(поправил номер выпуска)
 
(2 промежуточные версии не показаны)
Строка 1: Строка 1:
 +
{{Панель навигации по Qt Quarterly|Выпуск 26}}
 +
__NOTOC__
__NOTOC__
Строка 10: Строка 12:
*[[#takingthingsfurther | Taking Things Further]]
*[[#takingthingsfurther | Taking Things Further]]
</div>
</div>
-
When [[Qt:Документация 4.4.3/designer-manual | Qt Designer]] was redesigned and rewritten for Qt 4, one of the main aims was tomake it easier for developers to add their own custom controls to thestandard Qt widgets available to designers. Although the creators of [[Qt:Документация 4.4.3/designer-manual | Qt Designer]] hadC++ programmers in mind when they implemented this feature, such extensibilityisn't limited to just this one language&mdash;any set of language bindings that useQt's meta-object system can join in the fun.
+
When [[Qt:Документация 4.3.2/designer-manual | Qt Designer]] was redesigned and rewritten for Qt 4, one of the main aims was tomake it easier for developers to add their own custom controls to thestandard Qt widgets available to designers. Although the creators of [[Qt:Документация 4.3.2/designer-manual | Qt Designer]] hadC++ programmers in mind when they implemented this feature, such extensibilityisn't limited to just this one language&mdash;any set of language bindings that useQt's meta-object system can join in the fun.
[[Image:qq26-pyqtwidget.png|center]]
[[Image:qq26-pyqtwidget.png|center]]
-
In this article, we will show how to use PyQt to create widgets that can beused in [[Qt:Документация 4.4.3/designer-manual | Qt Designer]], describe the process of making plugins, and look at themechanisms that expose Qt's meta-object system to Python.
+
In this article, we will show how to use PyQt to create widgets that can beused in [[Qt:Документация 4.3.2/designer-manual | Qt Designer]], describe the process of making plugins, and look at themechanisms that expose Qt's meta-object system to Python.
<div id="creatingacustomwidget"></div>
<div id="creatingacustomwidget"></div>
===Creating a Custom Widget===
===Creating a Custom Widget===
-
PyQt exposes the Qt APIs to Python in a fairly conservative way, making theconstruction of custom widgets a familiar experience to C++ programmers. Newwidgets are subclassed from [[Qt:Документация 4.4.3/qwidget | QWidget]] in the usual way, as we can see withthis fully-functioning widget for entering latitude and longitude values:
+
PyQt exposes the Qt APIs to Python in a fairly conservative way, making theconstruction of custom widgets a familiar experience to C++ programmers. Newwidgets are subclassed from [[Qt:Документация 4.3.2/qwidget | QWidget]] in the usual way, as we can see withthis fully-functioning widget for entering latitude and longitude values:
<source lang="cpp-qt">
<source lang="cpp-qt">
class GeoLocationWidget(QWidget):
class GeoLocationWidget(QWidget):
Строка 68: Строка 70:
                   latitude)
                   latitude)
</source>  
</source>  
-
Just as in a C++ class, we define methods called <tt>latitude()</tt> and<tt>setLatitude()</tt> to provide the property's functionality. The declarationimmediately before the setter is a special Python decorator that tellsQt that <tt>setLatitude()</tt> is a slot and that it accepts double precisionfloating point values. Typically, this kind of declarationis not required with PyQt&mdash;any function or method can be used as aslot&mdash;but this makes interacting with [[Qt:Документация 4.4.3/designer-manual | Qt Designer]] easier later on.
+
Just as in a C++ class, we define methods called <tt>latitude()</tt> and<tt>setLatitude()</tt> to provide the property's functionality. The declarationimmediately before the setter is a special Python decorator that tellsQt that <tt>setLatitude()</tt> is a slot and that it accepts double precisionfloating point values. Typically, this kind of declarationis not required with PyQt&mdash;any function or method can be used as aslot&mdash;but this makes interacting with [[Qt:Документация 4.3.2/designer-manual | Qt Designer]] easier later on.
The <tt>pyqtProperty()</tt> function is used to register the property with Qt:
The <tt>pyqtProperty()</tt> function is used to register the property with Qt:
Строка 77: Строка 79:
===Producing a Plugin===
===Producing a Plugin===
-
The process of making the widget work is very similar tothat for C++ widgets. Each custom widget class is represented by aplugin class that creates instances of it (as described inQt Quarterly 16).The main difference is that [[Qt:Документация 4.4.3/designer-manual | Qt Designer]]'s plugin interfaces are used viaspecial PyQt-specific plugin classes.
+
The process of making the widget work is very similar tothat for C++ widgets. Each custom widget class is represented by aplugin class that creates instances of it (as described inQt Quarterly 16).The main difference is that [[Qt:Документация 4.3.2/designer-manual | Qt Designer]]'s plugin interfaces are used viaspecial PyQt-specific plugin classes.
[[Image:qq26-pyqtwidget-box.png|center]]
[[Image:qq26-pyqtwidget-box.png|center]]
Строка 93: Строка 95:
The code to initialize an instance of the class should be familiarto writers of C++ widget plugins.
The code to initialize an instance of the class should be familiarto writers of C++ widget plugins.
-
The <tt>initialize()</tt> method is called by [[Qt:Документация 4.4.3/designer-manual | Qt Designer]] after the plugin hasbeen loaded. Plugins use this opportunity to install extensions tothe form editor.
+
The <tt>initialize()</tt> method is called by [[Qt:Документация 4.3.2/designer-manual | Qt Designer]] after the plugin hasbeen loaded. Plugins use this opportunity to install extensions tothe form editor.
<source lang="cpp-qt">
<source lang="cpp-qt">
   def initialize(self, formEditor):
   def initialize(self, formEditor):
Строка 125: Строка 127:
</source>  
</source>  
-
For widgets created with Python, the <tt>includeFile()</tt> method returns thename of the module that provides the named class. In this case, the moduleresides within the [[Qt:Документация 4.4.3/qq_widgets | QQ_Widgets]] package.
+
For widgets created with Python, the <tt>includeFile()</tt> method returns thename of the module that provides the named class. In this case, the moduleresides within the [[Qt:Документация 4.3.2/qq_widgets | QQ_Widgets]] package.
<div id="makingamenu"></div>
<div id="makingamenu"></div>
===Making a Menu===
===Making a Menu===
-
Although it is quite easy to edit the properties of the custom widget in [[Qt:Документация 4.4.3/designer-manual | Qt Designer]]'sproperty editor, we will create a dialog that the user can open to change themin a more natural way. The dialog will be available from a new entry on theform's context menu whenever the user has selected the custom widget.
+
Although it is quite easy to edit the properties of the custom widget in [[Qt:Документация 4.3.2/designer-manual | Qt Designer]]'sproperty editor, we will create a dialog that the user can open to change themin a more natural way. The dialog will be available from a new entry on theform's context menu whenever the user has selected the custom widget.
The dialog itself isn't very special. It operates on a widget passed to itfrom elsewhere, providing another <tt>GeoLocationWidget</tt> for the user to modifyand preview changes in.
The dialog itself isn't very special. It operates on a widget passed to itfrom elsewhere, providing another <tt>GeoLocationWidget</tt> for the user to modifyand preview changes in.
Строка 209: Строка 211:
Note that the <tt>updateLocation()</tt> slot is not decorated in this case&mdash;sincewe make the connection, there's no need to declare it. Another short cut isthe use of a Python list in the <tt>taskActions()</tt> method.
Note that the <tt>updateLocation()</tt> slot is not decorated in this case&mdash;sincewe make the connection, there's no need to declare it. Another short cut isthe use of a Python list in the <tt>taskActions()</tt> method.
-
Each task menu extension is created by the task menu factory that weregistered in the <tt>initialize()</tt> method of our custom widget plugin.When the user opens a context menu over a custom widget, [[Qt:Документация 4.4.3/designer-manual | Qt Designer]] creates a newtask menu extension by calling the factory's <tt>createExtension()</tt> method.
+
Each task menu extension is created by the task menu factory that weregistered in the <tt>initialize()</tt> method of our custom widget plugin.When the user opens a context menu over a custom widget, [[Qt:Документация 4.3.2/designer-manual | Qt Designer]] creates a newtask menu extension by calling the factory's <tt>createExtension()</tt> method.
<source lang="cpp-qt">
<source lang="cpp-qt">
class GeoLocationTaskMenuFactory(QExtensionFactory):
class GeoLocationTaskMenuFactory(QExtensionFactory):
Строка 234: Строка 236:
===Putting Things in Place===
===Putting Things in Place===
-
On systems where [[Qt:Документация 4.4.3/designer-manual | Qt Designer]] is ableto use third party plugins, and where PyQt includes the <tt>QtDesigner</tt>module, it should be possible to use plugins written with PyQt. Unlike C++plugins, those written in Python do not have to be compiled or otherwiseprepared before we install them&mdash;we can simply copy the sources to theappropriate locations so that[[Qt:Документация 4.4.3/designer-manual | Qt Designer]] can find them.
+
On systems where [[Qt:Документация 4.3.2/designer-manual | Qt Designer]] is ableto use third party plugins, and where PyQt includes the <tt>QtDesigner</tt>module, it should be possible to use plugins written with PyQt. Unlike C++plugins, those written in Python do not have to be compiled or otherwiseprepared before we install them&mdash;we can simply copy the sources to theappropriate locations so that[[Qt:Документация 4.3.2/designer-manual | Qt Designer]] can find them.
-
The Python modules that provide the plugin and task menu extension aretypically stored together as files in the same directory. [[Qt:Документация 4.4.3/designer-manual | Qt Designer]] plugins writtenin C++ are usually installed in a <tt>designer</tt> subdirectory within thedirectory described by [[Qt:Документация 4.4.3/qlibraryinfo | QLibraryInfo]]::PluginPath. The convention forPython plugins is to create a <tt>python</tt> directory alongside the C++ pluginsand store them in there.
+
The Python modules that provide the plugin and task menu extension aretypically stored together as files in the same directory. [[Qt:Документация 4.3.2/designer-manual | Qt Designer]] plugins writtenin C++ are usually installed in a <tt>designer</tt> subdirectory within thedirectory described by [[Qt:Документация 4.3.2/qlibraryinfo | QLibraryInfo]]::PluginPath. The convention forPython plugins is to create a <tt>python</tt> directory alongside the C++ pluginsand store them in there.
The widgets themselves need to be placed in a standard location so that thePython interpreter can find them. Often, it is convenient to store them in thePython installation's <tt>site-packages</tt> directory since they are going to beneeded by applications that use forms containing those widgets. Theinstallation procedure involves creating a <tt>setup.py</tt> file which we won'tdiscuss here&mdash;the code archive accompanying this article contains a filethat can be used as a starting point for your own projects.
The widgets themselves need to be placed in a standard location so that thePython interpreter can find them. Often, it is convenient to store them in thePython installation's <tt>site-packages</tt> directory since they are going to beneeded by applications that use forms containing those widgets. Theinstallation procedure involves creating a <tt>setup.py</tt> file which we won'tdiscuss here&mdash;the code archive accompanying this article contains a filethat can be used as a starting point for your own projects.
-
As an alternative to installation, environment variables can be set to referto the locations of plugins and custom widgets. Developers familiar withPython will know that <tt>PYTHONPATH</tt> can be used to add new directories to thelist of known locations of modules and packages.Similarly, PY[[Qt:Документация 4.4.3/qtdesignerpath | QTDESIGNERPATH]] can be used to add locations of Python modulescontaining plugins and extensions for [[Qt:Документация 4.4.3/designer-manual | Qt Designer]].
+
As an alternative to installation, environment variables can be set to referto the locations of plugins and custom widgets. Developers familiar withPython will know that <tt>PYTHONPATH</tt> can be used to add new directories to thelist of known locations of modules and packages.Similarly, PY[[Qt:Документация 4.3.2/qtdesignerpath | QTDESIGNERPATH]] can be used to add locations of Python modulescontaining plugins and extensions for [[Qt:Документация 4.3.2/designer-manual | Qt Designer]].
-
With the plugin, extension and custom widget installed, or their locationsspecified using the environment variables described above, we can now run [[Qt:Документация 4.4.3/designer-manual | Qt Designer]]and use our custom widget&mdash;it should be available from the'''Qt Quarterly Examples''' section of the widget box.
+
With the plugin, extension and custom widget installed, or their locationsspecified using the environment variables described above, we can now run [[Qt:Документация 4.3.2/designer-manual | Qt Designer]]and use our custom widget&mdash;it should be available from the'''Qt Quarterly Examples''' section of the widget box.
Forms that contain custom widgets can be processed with the <tt>pyuic4</tt> tool ina way that should be familiar to users of <tt>uic</tt>. Code to import the customwidgets is generated along with the code to create the form, so developerssimply need to make sure that the modules containing them are available whentheir applications are run.
Forms that contain custom widgets can be processed with the <tt>pyuic4</tt> tool ina way that should be familiar to users of <tt>uic</tt>. Code to import the customwidgets is generated along with the code to create the form, so developerssimply need to make sure that the modules containing them are available whentheir applications are run.
Строка 252: Строка 254:
* We declared signals in advance, rather than just emitting custom signalswhen needed.  
* We declared signals in advance, rather than just emitting custom signalswhen needed.  
* We decorated certain methods with <tt>@pyqtSignature()</tt> to indicate thatthey are slots which accept certain argument types.  
* We decorated certain methods with <tt>@pyqtSignature()</tt> to indicate thatthey are slots which accept certain argument types.  
-
* We created properties with <tt>pyqtProperty()</tt> to expose them to [[Qt:Документация 4.4.3/designer-manual | Qt Designer]]'sproperty editor.  
+
* We created properties with <tt>pyqtProperty()</tt> to expose them to [[Qt:Документация 4.3.2/designer-manual | Qt Designer]]'sproperty editor.  
-
Since the use of each feature makes information available to other Qtcomponents, widgets written like this can be supplied to other[[Qt:Документация 4.4.3/qobject | QObject]]-based plugin systems as long as there is a way to executetheir Python source code.
+
Since the use of each feature makes information available to other Qtcomponents, widgets written like this can be supplied to other[[Qt:Документация 4.3.2/qobject | QObject]]-based plugin systems as long as there is a way to executetheir Python source code.
The use of slot and property declarations are also generally useful to Pythonprogrammers. Properties defined in this way behave just like normal Pythonproperties. The slot declarations can be used to help connect signals toslots in a class derived from a form.
The use of slot and property declarations are also generally useful to Pythonprogrammers. Properties defined in this way behave just like normal Pythonproperties. The slot declarations can be used to help connect signals toslots in a class derived from a form.
Строка 272: Строка 274:
Instructions for building and installing the widget plugins described in thisarticle are included in the examples archive,[http://www.crossplatform.ru/uploads/articles/sources/qq26-pyqtdesigner-1.0.zip available from the Qt Quarterly Web site]. We encourage you to try them out and apply the same techniques to your ownwidgets.
Instructions for building and installing the widget plugins described in thisarticle are included in the examples archive,[http://www.crossplatform.ru/uploads/articles/sources/qq26-pyqtdesigner-1.0.zip available from the Qt Quarterly Web site]. We encourage you to try them out and apply the same techniques to your ownwidgets.
-
More detailed instructions about [[Qt:Документация 4.4.3/designer-manual | Qt Designer]] plugins, PyQt and the Python tools thatmake them work together is available from the [http://www.riverbankcomputing.com/static/Docs/PyQt4/pyqt4ref.html RiverbankComputing Web site].
+
More detailed instructions about [[Qt:Документация 4.3.2/designer-manual | Qt Designer]] plugins, PyQt and the Python tools thatmake them work together is available from the [http://www.riverbankcomputing.com/static/Docs/PyQt4/pyqt4ref.html RiverbankComputing Web site].
-
[http://www.forum.crossplatform.ru/index.php?showtopic=1683 Обсудить на форуме...]
 
[[Категория:Qt_Издания]]
[[Категория:Qt_Издания]]

Текущая версия на 01:57, 20 февраля 2009

Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 26 | Документация


__NOTOC__

Содержание

[править] Designing Custom Controls with PyQt

by David Boddie

Одной из скрытых особенностей PyQt является его способность разрешить пользовательские плагины виджетов, которые будут созданы для Qt Designer'а, используя модули написаные на чистом Python. Это не только открывает интересные возможности для Python программистов, но и позволяет разработчикам и дизайнерам экспериментировать с быстрым прототипированием без нервотрепки создания общей библиотеки для плагинов.

When Qt Designer was redesigned and rewritten for Qt 4, one of the main aims was tomake it easier for developers to add their own custom controls to thestandard Qt widgets available to designers. Although the creators of Qt Designer hadC++ programmers in mind when they implemented this feature, such extensibilityisn't limited to just this one language—any set of language bindings that useQt's meta-object system can join in the fun.

center

In this article, we will show how to use PyQt to create widgets that can beused in Qt Designer, describe the process of making plugins, and look at themechanisms that expose Qt's meta-object system to Python.

[править] Creating a Custom Widget

PyQt exposes the Qt APIs to Python in a fairly conservative way, making theconstruction of custom widgets a familiar experience to C++ programmers. Newwidgets are subclassed from QWidget in the usual way, as we can see withthis fully-functioning widget for entering latitude and longitude values:

class GeoLocationWidget(QWidget):
 
  __pyqtSignals__ = ("latitudeChanged(double)",
                     "longitudeChanged(double)")
 
  def __init__(self, parent = None):
 
     QWidget.__init__(self, parent)
 
     latitudeLabel = QLabel(self.tr("Latitude:"))
     self.latitudeSpinBox = QDoubleSpinBox()
     self.latitudeSpinBox.setRange(-90.0, 90.0)
     self.latitudeSpinBox.setDecimals(5)
 
     longitudeLabel = QLabel(self.tr("Longitude:"))
     self.longitudeSpinBox = QDoubleSpinBox()
     self.longitudeSpinBox.setRange(-180.0, 180.0)
     self.longitudeSpinBox.setDecimals(5)
 
     self.connect(self.latitudeSpinBox,
         SIGNAL("valueChanged(double)"),
         self, SIGNAL("latitudeChanged(double)"))
     self.connect(self.longitudeSpinBox,
         SIGNAL("valueChanged(double)"),
         self, SIGNAL("longitudeChanged(double)"))
 
     layout = QGridLayout(self)
     layout.addWidget(latitudeLabel, 0, 0)
     layout.addWidget(self.latitudeSpinBox, 0, 1)
     layout.addWidget(longitudeLabel, 1, 0)
     layout.addWidget(self.longitudeSpinBox, 1, 1)

Two points of interest are worth noting. Firstly, unlike in Qt Jambi andQt Script, the syntax for connecting signals and slots follows the patternused in C++. Secondly, the signal declarations at the start of the classdefinition are not strictly required by PyQt—signals with arbitrary namescan be emitted without prior declaration—we will return to this pointlater.

Although the widget is useful as it stands, it doesn't expose any highlevel properties. As in C++, we define these by creating getter andsetter methods inside the class definition, and use some magic to exposethem to Qt's meta-object system. Here are the getter and setter methods forthe latitude property:

  def latitude(self):
     return self.latitudeSpinBox.value()
 
  @pyqtSignature("setLatitude(double)")
 
  def setLatitude(self, latitude):
 
     if latitude != self.latitudeSpinBox.value():
         self.latitudeSpinBox.setValue(latitude)
         self.emit(SIGNAL("latitudeChanged(double)"),
                   latitude)

Just as in a C++ class, we define methods called latitude() andsetLatitude() to provide the property's functionality. The declarationimmediately before the setter is a special Python decorator that tellsQt that setLatitude() is a slot and that it accepts double precisionfloating point values. Typically, this kind of declarationis not required with PyQt—any function or method can be used as aslot—but this makes interacting with Qt Designer easier later on.

The pyqtProperty() function is used to register the property with Qt:

  latitude = pyqtProperty("double", latitude, setLatitude)

Note that the latitude name is bound to the resulting property, andthe method we defined earlier is no longer directly accessible. If wewanted to keep it around, we could have used different names for thegetter and for the property.

[править] Producing a Plugin

The process of making the widget work is very similar tothat for C++ widgets. Each custom widget class is represented by aplugin class that creates instances of it (as described inQt Quarterly 16).The main difference is that Qt Designer's plugin interfaces are used viaspecial PyQt-specific plugin classes.

center

To keep things short, we will only look at part of the definition ofthe GeoLocationPlugin class:

class GeoLocationPlugin(QPyDesignerCustomWidgetPlugin):
 
   def __init__(self, parent = None):
 
      QPyDesignerCustomWidgetPlugin.__init__(self)
      self.initialized = False

The code to initialize an instance of the class should be familiarto writers of C++ widget plugins.

The initialize() method is called by Qt Designer after the plugin hasbeen loaded. Plugins use this opportunity to install extensions tothe form editor.

   def initialize(self, formEditor):
 
      if self.initialized:
          return
 
      manager = formEditor.extensionManager()
      if manager:
          self.factory = \
              GeoLocationTaskMenuFactory(manager)
          manager.registerExtensions(
              self.factory,
              "com.trolltech.Qt.Designer.TaskMenu")
 
      self.initialized = True

For this plugin, we install a task menu extension to let users configure ourcustom widget on the form via the context menu. This is done by creatingand registering a task menu factory with the form editor's extension manager.

There are a number of other methods that need to be implemented in the pluginclass, but the most important ones create individual widgets and provideinformation about the name and location of the custom widget class:

   def createWidget(self, parent):
      return GeoLocationWidget(parent)
 
   def name(self):
      return "GeoLocationWidget"
 
   def includeFile(self):
      return "QQ_Widgets.geolocationwidget"

For widgets created with Python, the includeFile() method returns thename of the module that provides the named class. In this case, the moduleresides within the QQ_Widgets package.

[править] Making a Menu

Although it is quite easy to edit the properties of the custom widget in Qt Designer'sproperty editor, we will create a dialog that the user can open to change themin a more natural way. The dialog will be available from a new entry on theform's context menu whenever the user has selected the custom widget.

The dialog itself isn't very special. It operates on a widget passed to itfrom elsewhere, providing another GeoLocationWidget for the user to modifyand preview changes in.

class GeoLocationDialog(QDialog):
 
   def __init__(self, widget, parent = None):
 
      QDialog.__init__(self, parent)
 
      self.widget = widget
 
      self.previewWidget = GeoLocationWidget()
      self.previewWidget.latitude = widget.latitude
      self.previewWidget.longitude = widget.longitude
 
      buttonBox = QDialogButtonBox()
      okButton = buttonBox.addButton(buttonBox.Ok)
      cancelButton = \
         buttonBox.addButton(buttonBox.Cancel)
 
      self.connect(okButton, SIGNAL("clicked()"),
                   self.updateWidget)
      self.connect(cancelButton, SIGNAL("clicked()"),
                   self, SLOT("reject()"))
 
      layout = QGridLayout()
      layout.addWidget(self.previewWidget, 1, 0, 1, 2)
      layout.addWidget(buttonBox, 2, 0, 1, 2)
      self.setLayout(layout)
 
      self.setWindowTitle(self.tr("Update Location"))

The interesting part is the slot where the changes to the preview widget arecommitted to the widget on the form:

   def updateWidget(self):
 
      formWindow = \
        QDesignerFormWindowInterface.findFormWindow(
            self.widget)
 
      if formWindow:
          formWindow.cursor().setProperty("latitude",
              QVariant(self.previewWidget.latitude))
          formWindow.cursor().setProperty("longitude",
              QVariant(self.previewWidget.longitude))
 
      self.accept()

Here, we make modifications via the form window's interface to ensure that theuser's changes are recorded. This way, if the user decides that they have madea mistake, they can simply undo the changes in the normal way.

The dialog is invoked from a custom task menu extension—an object thatconnects a menu entry with code to open a dialog. When created, thisprovides an Update Location... action that the form editor adds to itscontext menu. We connect this action to a slot so that we can open the dialogwhen the user selects the corresponding menu entry.

class GeoLocationMenuEntry(QPyDesignerTaskMenuExtension):
 
  def __init__(self, widget, parent):
 
      QPyDesignerTaskMenuExtension.__init__(self, parent)
 
      self.widget = widget
      self.editStateAction = QAction(
          self.tr("Update Location..."), self)
      self.connect(self.editStateAction,
          SIGNAL("triggered()"), self.updateLocation)
 
  def preferredEditAction(self):
      return self.editStateAction
 
  def taskActions(self):
      return [self.editStateAction]
 
  def updateLocation(self):
      dialog = GeoLocationDialog(self.widget)
      dialog.exec_()

Note that the updateLocation() slot is not decorated in this case—sincewe make the connection, there's no need to declare it. Another short cut isthe use of a Python list in the taskActions() method.

Each task menu extension is created by the task menu factory that weregistered in the initialize() method of our custom widget plugin.When the user opens a context menu over a custom widget, Qt Designer creates a newtask menu extension by calling the factory's createExtension() method.

class GeoLocationTaskMenuFactory(QExtensionFactory):
 
  def __init__(self, parent = None):
 
      QExtensionFactory.__init__(self, parent)
 
  def createExtension(self, obj, iid, parent):
 
      if iid != "com.trolltech.Qt.Designer.TaskMenu":
          return None
 
      if isinstance(obj, GeoLocationWidget):
          return GeoLocationMenuEntry(obj, parent)
 
      return None

The createExtension() method checks that the extension requested hasthe appropriate interface for a task menu extension, and only returns aninstance of one if the widget it is needed for is a GeoLocationWidget.

With the task menu factory creating GeoLocationTaskMenuEntry objects ondemand, the GeoLocationDialog class can be used to edit instances ofGeoLocationWidget when it is installed.

[править] Putting Things in Place

On systems where Qt Designer is ableto use third party plugins, and where PyQt includes the QtDesignermodule, it should be possible to use plugins written with PyQt. Unlike C++plugins, those written in Python do not have to be compiled or otherwiseprepared before we install them—we can simply copy the sources to theappropriate locations so that Qt Designer can find them.

The Python modules that provide the plugin and task menu extension aretypically stored together as files in the same directory. Qt Designer plugins writtenin C++ are usually installed in a designer subdirectory within thedirectory described by QLibraryInfo::PluginPath. The convention forPython plugins is to create a python directory alongside the C++ pluginsand store them in there.

The widgets themselves need to be placed in a standard location so that thePython interpreter can find them. Often, it is convenient to store them in thePython installation's site-packages directory since they are going to beneeded by applications that use forms containing those widgets. Theinstallation procedure involves creating a setup.py file which we won'tdiscuss here—the code archive accompanying this article contains a filethat can be used as a starting point for your own projects.

As an alternative to installation, environment variables can be set to referto the locations of plugins and custom widgets. Developers familiar withPython will know that PYTHONPATH can be used to add new directories to thelist of known locations of modules and packages.Similarly, PY QTDESIGNERPATH can be used to add locations of Python modulescontaining plugins and extensions for Qt Designer.

With the plugin, extension and custom widget installed, or their locationsspecified using the environment variables described above, we can now run Qt Designerand use our custom widget—it should be available from theQt Quarterly Examples section of the widget box.

Forms that contain custom widgets can be processed with the pyuic4 tool ina way that should be familiar to users of uic. Code to import the customwidgets is generated along with the code to create the form, so developerssimply need to make sure that the modules containing them are available whentheir applications are run.

[править] Behind the Scenes

When writing the GeoLocationWidget class, we used three features that arenot always needed when writing PyQt widgets:

  • We declared signals in advance, rather than just emitting custom signalswhen needed.
  • We decorated certain methods with @pyqtSignature() to indicate thatthey are slots which accept certain argument types.
  • We created properties with pyqtProperty() to expose them to Qt Designer'sproperty editor.

Since the use of each feature makes information available to other Qtcomponents, widgets written like this can be supplied to other QObject-based plugin systems as long as there is a way to executetheir Python source code.

The use of slot and property declarations are also generally useful to Pythonprogrammers. Properties defined in this way behave just like normal Pythonproperties. The slot declarations can be used to help connect signals toslots in a class derived from a form.

   @pyqtSignature("on_pushButton_clicked()")
   def on_pushButton_clicked(self):
      ...
      self.listWidget.addItem(
            u"%i\\xb0 %i' %i\\" N, " % self.dms(lat) + \
            u"%i\\xb0 %i' %i\\" E" % self.dms(lng))

In this case, the decorator ensures that the method is only called when a pushbutton's clicked() signal is emitted, but not when its clicked(bool)signal is emitted.

[править] Taking Things Further

Instructions for building and installing the widget plugins described in thisarticle are included in the examples archive,available from the Qt Quarterly Web site. We encourage you to try them out and apply the same techniques to your ownwidgets.

More detailed instructions about Qt Designer plugins, PyQt and the Python tools thatmake them work together is available from the RiverbankComputing Web site.