1

I am learning Qt and how to create GUIs with python. I managed to create my own Qt files and fill it with buttons and other simple things, but now I found this amazing attitude indicator

This ai.py file contains an attitude widget that I would like to import in my own GUI. So I designed my .ui file with an empty widget named "viz_widget", then I wrote this python file

# -*- coding: utf-8 -*-

import sys
from PyQt4 import QtCore, QtGui, uic
from ai import AttitudeIndicator

qtCreatorFile1 = "mainwindow.ui" # Enter file here.


Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile1)


class OperatorGUI(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(OperatorGUI, self).__init__(parent)
        QtGui.QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        self.setupUi(self)
        self.viz_widget = AttitudeIndicator()
        self.viz_widget.setPitch(10)
        self.viz_widget.setRoll(20)
        self.viz_widget.setHover(500/10.)
        self.viz_widget.setBaro(500/10.)   
        self.viz_widget.update()

    # Key press functions
    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Q: #Q: close the window
            print "pressed Q: exit by keyboard"
            self.close()

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = OperatorGUI()
    window.show()
    sys.exit(app.exec_())

The GUI is launched, there aren't any errors, but I can not display the attitude widget into my GUI. Is it possible to import the widget? What is my error?

Thank you in advance

EDIT: this is the file maiwindow.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <widget class="QWidget" name="viz_widget" native="true">
    <property name="geometry">
     <rect>
      <x>50</x>
      <y>40</y>
      <width>671</width>
      <height>441</height>
     </rect>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>20</height>
    </rect>
   </property>
  </widget>
  <widget class="QToolBar" name="mainToolBar">
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>
marcoresk
  • 1,837
  • 2
  • 17
  • 29
  • You probably just need to add the widget to the window's layout - or rather, to the layout of its central widget. – ekhumoro Sep 01 '17 at 13:49
  • @ekhumoro I added a widget in my window (ui.file) named viz_widget in QtCreator. Then I connected it to the object AttitudeIndicator in the line `self.viz_widget = AttitudeIndicator()`. Do I have to do something more or something different? – marcoresk Sep 01 '17 at 14:02
  • Firstly: get rid of those two `__init__` calls - they are not needed if you use `super`. Secondly, remove the widget you added in QtCreator, so you just have an empty main-window (and save the changes). Then add this line to the end of `__init__`: `layout = QtGui.QVBoxLayout(self.centralWidget())`. Finally, add the widget to the layout: `layout.addWidget(self.viz_widget)`. – ekhumoro Sep 01 '17 at 14:17
  • @ekhumoro Thank you. I will work on that (your first command seems to give error to me) but I would like to ask if it is possible (at least in theory) to insert this external widget in my GUI (in the place where I put the "empty" widget) – marcoresk Sep 01 '17 at 15:12
  • If you want to literally replace a widget at runtime, you can use widget promotion. See [this answer](https://stackoverflow.com/a/42076247/984421) for an example of how to do this. – ekhumoro Sep 01 '17 at 15:37
  • PS: I tested the code in my comment above using your example and it all works exactly as expected. Are you sure you followed all the steps correctly? – ekhumoro Sep 01 '17 at 15:45
  • @ekhumoro I had several tries both with your lines of code and with widget promotion, but I can't obtain the widget displayed. May I ask you a final effort, providing a full answer with your code? I hope I will learn something more and I wish to mark your answer as accepted. – marcoresk Sep 02 '17 at 09:40
  • Please see my answer for an explanation of how to achieve both options. – ekhumoro Sep 03 '17 at 13:03

1 Answers1

1

Here's the steps needed to add an AttitudeIndicator widget both in code, and via promotion in Qt Designer.

Firstly, you will need to make some changes to the "mainwindow.ui" file. So, in Qt designer, click on the central-widget in the Object Inspector, and then in the Property Editor, change the objectName to central_widget. This is important, because the current name is shadowing the QMainWindow.centralWidget() method that we need to use later. Secondly, with the central-widget still selected, click Lay Out Horizontally on the toolbar (the icon has three blue vertical bars) - and save the changes.

Now right-click the viz_widget (either in the Object Inspector or on the form), choose Promote to ..., and enter MyAttitudeIndicator in Promoted class name, and ai_widget in Header file. Then click Add and Promote - and save the changes. After doing that, you should see the Class change from QWidget to MyAttitudeIndicator in the Object Inspector. (But note that the actual widget on the form will not be displayed as an AttitudeIndicator. To do that, you'd need to write a custom designer plugin, which goes way beyond the scope of this question).

To make use of these changes, some code needs to be added. Firstly, you need to create a module called ai_widget.py for the promoted widget class. The module should contain this code:

from ai import AttitudeIndicator

class MyAttitudeIndicator(AttitudeIndicator):
    def __init__(self, parent, hz=30):
        super(MyAttitudeIndicator, self).__init__(hz=hz)
        self.setParent(parent)

The main reason why this class is needed is because the original AttitudeIndicator has a buggy API. The constructor really needs to take a parent widget (which is fixed in the above code). However, you could also use this class to add your own custom features.

The final step is to add some changes to the main script, which is shown below. Note that all the widgets added in Qt Designer will become attributes of the object passed to setupUi() (i.e. the main-window, self, in this case).

import sys
from PyQt4 import QtCore, QtGui, uic
from ai import AttitudeIndicator

qtCreatorFile1 = "mainwindow.ui" # Enter file here.

Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile1)

class OperatorGUI(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(OperatorGUI, self).__init__(parent)
        self.setupUi(self)

        # promoted widget from qt designer
        self.viz_widget.setPitch(10)
        self.viz_widget.setRoll(20)
        self.viz_widget.setHover(500/10.)
        self.viz_widget.setBaro(500/10.)

        # optional second widget
        self.viz_widget2 = AttitudeIndicator()
        self.viz_widget2.setPitch(10)
        self.viz_widget2.setRoll(-40)
        layout = self.centralWidget().layout()
        layout.addWidget(self.viz_widget2)

    # Key press functions
    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Q: #Q: close the window
            print "pressed Q: exit by keyboard"
            self.close()

if __name__ == "__main__":

    app = QtGui.QApplication(sys.argv)
    window = OperatorGUI()
    window.show()
    sys.exit(app.exec_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thank you very much. The second strategy works wonderfully (maybe the central widget name made the trick). Promotion seems very interesting but it still does not work and give me this error `super(MyAttitudeIndicator, self).__init__(hz=hz) TypeError: __init__() got an unexpected keyword argument 'hz'`. I have no clue about `hz`. But you found at least one solution working and I accept the answer and thank you again for your help. – marcoresk Sep 03 '17 at 16:13
  • @marcoresk. No problem. I used the current "ai.py" from [github, which has that `hz` argument](https://github.com/capriele/crazyflie-clients-python-move/blob/master/build/lib.linux-i686-2.7/cfclient/ui/widgets/ai.py#L45). My code has been thoroughly tested, so I know it all works correctly. – ekhumoro Sep 03 '17 at 16:24
  • I discovered that, with no awareness, deleting hz=hz in ai_widget the promotion seems to work (at least it appears in the GUI). Thank you again. – marcoresk Sep 03 '17 at 16:28