Author Topic: Pythonic solution for accessing class properties  (Read 980 times)

0 Members and 1 Guest are viewing this topic.

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2065
  • Country: fi
    • My home page and email address
Re: Pythonic solution for accessing class properties
« Reply #25 on: November 18, 2020, 06:01:21 pm »
I catch [KeyboardInterrupts] more globally:
Makes sense.  (If anything in main() needs cleanup, it can catch KeyboardInterrupt and just re-raise it via raise, without any parameters.)

Distinguishing different type messages by instantiating them from different subclasses is cool.
It is relevant to this thread, too, because when one gets a widget, one can use isinstance() to check which kind of widget it is, and use the Qt type hierarchy to avoid having to check for all types, just those that matter.

For example, when saving widget contents, layout widgets don't matter (because they themselves don't have any user-editable content, it is in child widgets).  So, one can check isinstance(widget, QtWidgets.QLayout) and it will be true for QLayouts, QBoxLayouts, QHBoxLayouts, QVBoxLayouts, QStackedLayouts, QGridLayouts, and QFormLayouts, and any instances of their subclasses too.

An another example: QAbstractButton is the parent type for all sorts of buttons, but of these, really only QCheckBox and QRadioButton have state that makes sense to record.  The others, QPushButton, QToolButton, and QCommandLinkButton, are used for immediate interaction, so they don't normally stay "pressed" like checkboxes and radiobuttons do.

Or provoking a illuminating TypeError with three more characters. (It was my 2nd edit while you were composing your message.)
Yuh, raise TypeError("timeout must be a real number or None, not " + type(timeout)) or something along those lines. (That's the exception math.sqrt(None) raises.)

I'm not actually spending much time with these, just hoping to provide enough information to help others in the right direction.  For me, writing the above example code snippets was about as much effort as playing a couple of rounds of Sudoku or Solitaire; and while I try to make sure they're helpful, I don't polish them like I would if I were to e.g. recommend them as a pattern; I personally always need to sleep and review the code I've written with a new pair of eyes, before I can trust it at all.
(Sometimes my mind gets stuck on one particular flow, and cannot see the alternatives.  Having a good nights sleep, letting my subconscious sort it out, always helps.)
 

Offline rx8pilot

  • Super Contributor
  • ***
  • Posts: 3604
  • Country: us
  • If you want more money, be more valuable.
Re: Pythonic solution for accessing class properties
« Reply #26 on: November 19, 2020, 01:31:27 am »
Just for fun, I wrote a stupid (and probably buggy!) but simple example of how to use Qt5 QThread and Qt signals and slots to pass serial device commands, requests, and responses between the main thread and the serial worker thread. .......


I am VERY grateful for the details on this topic!!!!! The various resources I have been scouring tell me 'what' to do and not much 'why' to do it. All of the effort you put into this thread speaks to building an intuition of how the problems are solved instead of just just a simple copy-paste code snippet that would be over my head.

So much to chew on, thank you!



Factory400 - the worlds smallest factory. https://www.youtube.com/c/Factory400
 

Offline rx8pilot

  • Super Contributor
  • ***
  • Posts: 3604
  • Country: us
  • If you want more money, be more valuable.
Re: Pythonic solution for accessing class properties
« Reply #27 on: November 19, 2020, 04:38:50 am »
I am getting stuck here:
Code: [Select]
def prgsUpdate(self, barNumber, minVal, maxVal, remainVal):
        ''' Update progress bar specified by barNumber 0-6 '''
        widget = self.ui.findChild("cycleRemain" + barNumber)
        widget.setMinimum(minVal)
        widget.setMaximum(maxVal)
        widget.setProperty("value", remainVal)

findChild() is not an attribute of the object 'widget'.

I tried a few variants but have not been able to figure it out. It looks like it take 2 arguments - which I tried but since findChild is not found at all, it doesn't really matter.
Factory400 - the worlds smallest factory. https://www.youtube.com/c/Factory400
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2065
  • Country: fi
    • My home page and email address
Re: Pythonic solution for accessing class properties
« Reply #28 on: November 19, 2020, 04:57:43 am »
Sorry, my fault.  The first parameter is the desired widget type, which for all GUI widgets is QtWidgets.QWidget, or for all possible Qt objects QtCore.QObject.
I fixed my post, but here it is again:
Code: [Select]
def prgsUpdate(self, barNumber, minVal, maxVal, remainVal):
        ''' Update progress bar specified by barNumber 0-6 '''
        widget = self.ui.findChild(QtWidgets.QWidget, "cycleRemain" + barNumber)
        widget.setMinimum(minVal)
        widget.setMaximum(maxVal)
        widget.setProperty("value", remainVal)
 

Offline rx8pilot

  • Super Contributor
  • ***
  • Posts: 3604
  • Country: us
  • If you want more money, be more valuable.
Re: Pythonic solution for accessing class properties
« Reply #29 on: November 19, 2020, 05:11:05 am »
Ok, I had tried that but the error remains.....

Code: [Select]
from PyQt5 import QtWidgets as qtw
.........
Ui_shopDash, baseClass = uic.loadUiType('ui\cncDash_A-v2.ui')
..........
prgsBar = self.ui.findChild(qtw.QWidget, "cycleRemain" + barNumber)
.........

error:
Code: [Select]
prgsBar = self.ui.findChild(qtw.QWidget, "cycleRemain" + str(barNumber))
AttributeError: 'Ui_shopDash' object has no attribute 'findChild'

 :-//





Factory400 - the worlds smallest factory. https://www.youtube.com/c/Factory400
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2065
  • Country: fi
    • My home page and email address
Re: Pythonic solution for accessing class properties
« Reply #30 on: November 19, 2020, 05:54:19 am »
Code: [Select]
from PyQt5 import QtWidgets as qtw
.........
Ui_shopDash, baseClass = uic.loadUiType('ui\cncDash_A-v2.ui')
..........
prgsBar = self.ui.findChild(qtw.QWidget, "cycleRemain" + barNumber)
.........
Because you are using loadUiType(), you need to use baseClass.findChild(qtw.QWidget, "cycleRemain" + barNumber).

I am using self.ui = uic.loadUi('ui-file-path'), which returns an instance (actually, the widget hierarchy itself).  Because the self.ui instance is necessarily some subclass of QtCore.QObject, it does have the .findChild() method.
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2065
  • Country: fi
    • My home page and email address
Re: Pythonic solution for accessing class properties
« Reply #31 on: November 19, 2020, 06:15:00 am »
Consider the following Python2/Python3 example program, example.py:
Code: [Select]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
try:
    from PyQt5 import QtCore, QtGui, QtWidgets
    from PyQt5.QtCore import pyqtSignal as Signal, pyqtSlot as Slot
    from PyQt5.uic import loadUi
except:
    try:
        from PySide2 import QtCore, QtGui, QtWidgets
        from PySide2.QtCore import Signal, Slot
        from uic import loadUi
    except:
        raise ModuleNotFoundError("No Qt5 support.")

def _iterate_all_qobjects(obj):
    yield obj
    for subobj in obj.children():
        for childobj in _iterate_all_qobjects(subobj):
            yield childobj

class Ui(QtCore.QObject):
    """Graphical User interface class"""

    def __init__(self, uifile, parent=None):
        QtCore.QObject.__init__(self, parent)
        self.ui = loadUi(uifile)

    def show(self):
        self.ui.show()

    def allTextWidgets(self):
        for widget in _iterate_all_qobjects(self.ui):
            if len(widget.objectName()) < 1:
                continue

            if isinstance(widget, QtWidgets.QLineEdit):
                yield (widget.objectName(), widget.text())
            elif isinstance(widget, QtWidgets.QPlainTextEdit):
                yield (widget.objectName(), widget.toPlainText())
            elif isinstance(widget, QtWidgets.QTextEdit):
                yield (widget.objectName(), widget.toPlainText())

if __name__ == '__main__':
    if len(sys.argv) < 2 or '-h' in sys.argv[1:] or '--help' in sys.argv[1:]:
        if len(sys.argv) > 0 and len(sys.argv[0]) > 0:
            this = sys.argv[0]
        else:
            this = '(this)'

        sys.stderr.write('\n')
        sys.stderr.write('Usage: %s [ -h | --help ]\n' % this)
        sys.stderr.write('       %s UI-FILE [ textwidgetname=contents ]\n' % this)
        sys.stderr.write('\n')
        sys.stderr.write('When you close the window, the contents of all named text widgets are shown.\n')
        sys.stderr.write('\n')
        sys.exit(0)

    QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
    app = QtWidgets.QApplication(sys.argv)
    win = Ui(sys.argv[1])
    win.show()

    # Set text widget contents
    if len(sys.argv) > 2:
        for arg in sys.argv[2:]:
            if '=' in arg:
                name, value = arg.split('=', 2)
                widget = win.ui.findChild(QtCore.QObject, name)
                if widget is None:
                    sys.stderr.write("%s: No such widget in %s." % (name, sys.argv[1]))
                elif isinstance(widget, (QtWidgets.QLineEdit, QtWidgets.QLabel)):
                    widget.setText(value)
                elif isinstance(widget, (QtWidgets.QPlainTextEdit, QtWidgets.QTextEdit)):
                    widget.setDocument(QtGui.QTextDocument(value))
                else:
                    sys.stderr.write("%s: Widget is %s, not a text widget.\n" % (name, type(widget)))

    status = app.exec_()
    for pair in win.allTextWidgets():
        print('%s: "%s"' % pair)
    del win, app
    sys.exit(status)
If you use PySide2, put the uic.py I showed earlier in this thread into the same directory.

When you run it, give it the path to an .ui file, and optionally one or more name=text pairs.

Here is again the simple main.ui I used for testing:
Code: [Select]
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Ui</class>
 <widget class="QWidget" name="Ui">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>368</width>
    <height>172</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Serial Example</string>
  </property>
  <layout class="QGridLayout" name="layout" rowstretch="0,0,1" columnstretch="0,1,0,0">
   <item row="0" column="0" alignment="Qt::AlignRight">
    <widget class="QLabel" name="deviceLabel">
     <property name="text">
      <string>Device:</string>
     </property>
    </widget>
   </item>
   <item row="2" column="0" alignment="Qt::AlignRight|Qt::AlignTop">
    <widget class="QLabel" name="responseLabel">
     <property name="text">
      <string>Response:</string>
     </property>
    </widget>
   </item>
   <item row="0" column="1">
    <widget class="QLineEdit" name="deviceEdit">
     <property name="text">
      <string>/dev/ttyACM0</string>
     </property>
    </widget>
   </item>
   <item row="1" column="0" alignment="Qt::AlignRight">
    <widget class="QLabel" name="commandLabel">
     <property name="text">
      <string>Command:</string>
     </property>
    </widget>
   </item>
   <item row="0" column="2">
    <widget class="QPushButton" name="connectButton">
     <property name="text">
      <string>Connect</string>
     </property>
    </widget>
   </item>
   <item row="0" column="3">
    <widget class="QPushButton" name="disconnectButton">
     <property name="text">
      <string>Disconnect</string>
     </property>
    </widget>
   </item>
   <item row="1" column="1" colspan="3">
    <widget class="QLineEdit" name="commandEdit"/>
   </item>
   <item row="2" column="1" colspan="3">
    <widget class="QTextEdit" name="responseText">
     <property name="lineWrapMode">
      <enum>QTextEdit::NoWrap</enum>
     </property>
     <property name="readOnly">
      <bool>true</bool>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>
Run it using e.g. python example.py main.ui. Note the initial contents of the fields.

In example.py, function _iterate_all_qobjects(obj) is a generator function which yields each object in the qobject tree recursively.

When you close the window, the allTextWidgets() generator method of the Ui class iterates over all widgets in the Ui hierarchy, skipping widgets without names, and generates tuples of (text widget name, editable text widget contents).  In the main, these are simply printed to standard output.

When the command line also contains name=text pairs, the interesting magic happens.  For example, try running
    python example.py main.ui deviceEdit=/dev/null commandEdit=Nothing responseText=Hey

This is done in the snippet beginning with comment # Set text widget contents.
The first four lines split the command-line parameter to a name=value pair.
The fifth line finds the QObject in the UI hierarchy having that name.
QLineEdit and QLabel widgets provide a setText() method that we use to set the contents.
QPlainTextEdit and QTextEdit widgets provide a setDocument() method that we use, first constructing a suitable QtGui.QTextDocument for it (its constructor takes the plain text contents as a string).

So, as you can see, with uic.loadUi() this works just fine.  Note that PySide2 nor the uic.py shim that I've shown in this thread, *does not* provide loadUiType(), only loadUi().
 

Offline rx8pilot

  • Super Contributor
  • ***
  • Posts: 3604
  • Country: us
  • If you want more money, be more valuable.
Re: Pythonic solution for accessing class properties
« Reply #32 on: November 20, 2020, 11:49:59 pm »
I got my code to work as I hoped in the original post! Very exciting.

My goal with this thread (and all my other threads) is not to have a solution handed to me, but rather gain the understanding necessary for me to build the intuition necessary to solve this problem and the ones in the future.  :-+

This discussion gave me a lot to dig into and the end result was something that hopefully others can benefit from. Thanks Nominal Animal for the time it took to post your knowledge and the examples. It kept me busy for the past couple of days. Still have a long journey toward expert level Python but this is a much needed confidence booster.

Awesome.
Factory400 - the worlds smallest factory. https://www.youtube.com/c/Factory400
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf