2

I have a GUI where user selects gas components from list and moves it to 'Chosen' and another button takes text from 'Chosen' that has columns: Gas Component, Molecular Weight, Mol%. The first two columns get information from a dictionary that i've created, and last column is user input.

When button is clicked and all values are filled in 'Chosen', it will ask user for a number, n = 1-6 , it will then take rows with n highest mol% in 'Chosen' and create n rows in Results and add Gas Component text with n highest mol% values, to first column in 'Results'

I am currently using dictionaries to keep track of information.

    def calculategas(self):
        #makes sure dictionaries are clear for any errors on rerunning button
        self.sortedmol.clear()
        self.componentDic1.clear()
        self.componentDic2.clear()
        self.componentDic3.clear()
        self.mmDict.clear()
        self.mfDict.clear()
        self.mDict.clear()
        self.massFracDict.clear()
        self.molarmassDict.clear()
        self.item_.clear()
        self.lookup.clear()

        root = self.chosen.invisibleRootItem()
        child_count = root.childCount()
        for i in range(child_count):
            item = root.child(i)
            #Takes text from self.chosen QTreeWidget (Top-right)
            component = item.text(0)
            molWeight = item.text(1)
            componentMol = float(item.text(2))
            #creates dictionary of items in self.chosen
            self.componentDic1[component] = componentMol
            self.componentDic2[molWeight] = componentMol
            #Sorts dictionaries above from highest to lowest
            self.sortedmol = dict(sorted(self.componentDic1.items(), key=operator.itemgetter(1),
                                    reverse=True)) # Sorted component list - largest to smallest mol%
            self.sortedmolar = dict(sorted(self.componentDic2.items(), key=operator.itemgetter(1),
                                      reverse=True))  # Sorted molar mass list - largest to smallest mol%
            # change values of self.sortedmol with keys of self.sortedmolar
            self.lookup = {v:k for k, v in self.sortedmol.items()}
            self.componentDic3 = {self.lookup[v]: float(k) for k, v in self.sortedmolar.items()}
            ##Copies so original doesn't change
            self.mmDict = self.sortedmol.copy()
            self.mfDict = self.mmDict.copy()
            self.mDict = self.componentDic3.copy()

            ###Calculations
            self.molarmassDict = {k: round(v * self.mmDict[k] / 100, 3) for k, v in self.mDict.items() if
                                  k in self.mmDict}
            summolmDict = round(sum(self.molarmassDict.values()), 3)
            self.massFracDict = {k: round(self.molarmassDict[k] / summolmDict, 3) for k, v in self.molarmassDict.items()
                                 if
                                 k in self.molarmassDict}
        componentNum, ok = QInputDialog.getText(None, 'Number of components', 'How many components do you wish to use?')
        if (ok):
            #Remove any items in result QTreeWidget
            current_item = self.result.invisibleRootItem()
            children = []
            for child in range(current_item.childCount()):
                children.append(current_item.child(child))
            for child in children:
                current_item.removeChild(child)
            #Adds rows to self.result QTreeWidget
            for i in range(int(componentNum)):
                self.item_[i] = QtWidgets.QTreeWidgetItem(self.result)
                self.item_[i].setFlags(
                    QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
            if len(self.sortedmol) > int(componentNum):  # takes only # of components user wants
                ##Adds the number of components user inputs with highest mol% to self.result
                root = self.result.invisibleRootItem()
                child_count = root.childCount()
                for i in range(child_count):
                    item = root.child(i)
                    item.setText(0, str(list(self.massFracDict)[i]))  # update first column with dictionary keys
            else:
                ###This section will change
                root = self.result.invisibleRootItem()
                child_count = root.childCount()
                for i in range(child_count):
                    item = root.child(i)
                    item.setText(0, str(list(self.massFracDict)[i]))  # update first column with dictionary keys

Currently everything works except that when there is a duplicate value in mol% or molecular weight it will tend to skip it.

Dictionary self.sortedmol:

Keys = Gas Component text

Values = mol% text

Dictionary self.sortedmolar:

Keys = Molecular Weight text

Values = mol% text

Problems:

  • If two components have same molecular weight, it will ignore it

  • If two components have same mol%, it will ignore it

    • Overall goal: Add rows with n highest mol% in 'Chosen' to 'Result and keep values for later calculations.

    • Question: Is there any way to fix this error, or use another way to get same desired results?

Step1:

enter image description here

Step2:

enter image description here

Drees
  • 688
  • 1
  • 6
  • 21
  • provide a [mcve], what are *desired results*? – eyllanesc May 01 '19 at 19:00
  • @eyllanesc The desired results are shown in the attached image. Take n(number given by user) highest 'Gas Components' from top QTreeWidget and move it to bottom QTreeWidget and keep values from top for later. Also, where do I upload MCVE? – Drees May 01 '19 at 19:03
  • 1) Okay, you refer to the n higher components with respect to mol%, am I correct? 2) in your question, therefore it must be minimal but without losing the other characteristics: complete and verifiable. If I am correct in 1) then I will provide my code without taking your code as a basis for obvious reasons – eyllanesc May 01 '19 at 19:07
  • @eyllanesc Yes, you are correct. – Drees May 01 '19 at 19:17
  • Test my solution and tell me if it is what you want. – eyllanesc May 01 '19 at 19:45

1 Answers1

1

If you want to get the n rows where the mol% are the highest then you must use a QSortFilterProxyModel to sort, and another to filter.

from PyQt5 import QtCore, QtGui, QtWidgets

ListRole = QtCore.Qt.UserRole
VisibleRole = QtCore.Qt.UserRole + 1


class TopProxyModel(QtCore.QSortFilterProxyModel):
    @property
    def number(self):
        if not hasattr(self, "_number"):
            self._number = -1
        return self._number

    @number.setter
    def number(self, number):
        self._number = number
        self.invalidateFilter()

    def filterAcceptsRow(self, sourceRow, sourceParent):
        if self.number < 0:
            ix = self.sourceModel().index(sourceRow, 2)
            self.sourceModel().setData(ix, False, VisibleRole)
            return True
        return sourceRow < self.number


class BlankDelegate(QtWidgets.QStyledItemDelegate):
    def initStyleOption(self, option, index):
        super().initStyleOption(option, index)
        if not index.data(VisibleRole):
            option.text = ""

    def setModelData(self, editor, model, index):
        sm = model
        ix = index
        while hasattr(sm, "sourceModel"):
            ix = sm.mapToSource(ix)
            sm = sm.sourceModel()
        sm.setData(ix, editor.value(), QtCore.Qt.DisplayRole)
        if not sm.data(ix, VisibleRole):
            sm.setData(ix, True, VisibleRole)


class ReadOnlyDelegate(QtWidgets.QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        return None


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        datas = [
            ("IsonButane", 58.12, 13),
            ("IsonPentane", 75.12, 3),
            ("Methane", 16.04, 5),
            ("Nitrogen", 28.01, 5),
            ("Hexane", 86.17, 5),
            ("Hydrogen", 2.02, 13),
            ("Hydrogen Sulfide", 34.08, 2),
        ]

        add_button = QtWidgets.QPushButton(">>>", clicked=self.add_row)
        remove_button = QtWidgets.QPushButton("<<<", clicked=self.remove_row)
        select_button = QtWidgets.QPushButton("Calculate", clicked=self.select)
        sp = select_button.sizePolicy()
        sp.setHorizontalPolicy(QtWidgets.QSizePolicy.Maximum)
        select_button.setSizePolicy(sp)

        self.listwidget = QtWidgets.QListWidget(
            selectionMode=QtWidgets.QAbstractItemView.MultiSelection
        )
        for data in datas:
            item = QtWidgets.QListWidgetItem(data[0])
            item.setData(ListRole, data)
            self.listwidget.addItem(item)
        self.tree_widget = QtWidgets.QTreeWidget(
            columnCount=3,
            indentation=0,
            selectionMode=QtWidgets.QAbstractItemView.MultiSelection,
        )
        for i, T in enumerate(
            (ReadOnlyDelegate, ReadOnlyDelegate, BlankDelegate)
        ):
            delegate = T(self.tree_widget)
            self.tree_widget.setItemDelegateForColumn(2, delegate)

        self.tree_widget.setHeaderLabels(
            ["Gas Component", "Molecular Weight", "Mol%"]
        )
        self.tree_view = QtWidgets.QTreeView(indentation=0)

        proxy_sort = QtCore.QSortFilterProxyModel(self)
        proxy_sort.setSourceModel(self.tree_widget.model())
        proxy_sort.sort(2, QtCore.Qt.DescendingOrder)

        self.proxy_top = TopProxyModel(self)
        self.proxy_top.number = 0
        self.proxy_top.setSourceModel(proxy_sort)
        self.tree_view.setModel(self.proxy_top)

        lay = QtWidgets.QGridLayout(self)
        lay.addWidget(QtWidgets.QLabel("<b>Available Gases:</b>"), 0, 0)
        lay.addWidget(self.listwidget)

        vlay = QtWidgets.QVBoxLayout()
        vlay.addStretch()
        vlay.addWidget(add_button)
        vlay.addWidget(remove_button)
        vlay.addStretch()
        lay.addLayout(vlay, 1, 1)
        lay.addWidget(QtWidgets.QLabel("<b>Chosen Gases</b>"), 0, 2)
        lay.addWidget(self.tree_widget, 1, 2)

        lay.addWidget(select_button, 2, 2, alignment=QtCore.Qt.AlignCenter)
        lay.addWidget(QtWidgets.QLabel("<b>Result:</b>"), 3, 2)
        lay.addWidget(self.tree_view, 4, 2)

    @QtCore.pyqtSlot()
    def add_row(self):
        for item in self.listwidget.selectedItems():
            data = item.data(ListRole)
            text = item.text()
            if self.tree_widget.findItems(text, QtCore.Qt.MatchExactly):
                continue
            it = self.listwidget.takeItem(self.listwidget.row(item))
            item = QtWidgets.QTreeWidgetItem()
            self.tree_widget.addTopLevelItem(item)
            item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
            for i, e in enumerate(data):
                item.setData(i, QtCore.Qt.DisplayRole, e)

    @QtCore.pyqtSlot()
    def remove_row(self):
        rows = [
            self.tree_widget.indexOfTopLevelItem(item)
            for item in self.tree_widget.selectedItems()
        ]
        for row in sorted(rows, reverse=True):
            item = self.tree_widget.takeTopLevelItem(row)
            data = []
            for i in range(self.tree_widget.columnCount()):
                data.append(item.data(i, QtCore.Qt.DisplayRole))
            it = QtWidgets.QListWidgetItem(data[0])
            it.setData(ListRole, data)
            self.listwidget.addItem(it)
            if item is not None:
                del item

    @QtCore.pyqtSlot()
    def select(self):
        last_number = max(self.proxy_top.number, 0)
        number, ok = QtWidgets.QInputDialog.getInt(
            None,
            "Number of components",
            "How many components do you wish to use?",
            last_number,
            min=-1,
            max=self.tree_widget.topLevelItemCount(),
        )
        if ok:
            self.proxy_top.number = number
        for i in range(self.tree_widget.topLevelItemCount()):
            it = self.tree_widget.topLevelItem(i)
            it.setData(2, VisibleRole, False)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • @eyllansec How would I make it to where the mol% column is user input rather than defined and when button is clicked it takes those values and stores them somewhere for future? – Drees May 01 '19 at 19:57
  • @Drees I do not understand you, explain yourself better – eyllanesc May 01 '19 at 20:00
  • @Drees If you want to change the column, you only have to change the 2 in `proxy_sort.sort(2, QtCore.Qt.DescendingOrder)` – eyllanesc May 01 '19 at 20:01
  • @Drees Do you want the user to choose the column where is the%? – eyllanesc May 01 '19 at 20:04
  • @eyllansec To your last comment, the answer is yes. I updated my post with images of what i'm trying to do. The user selects multiple gases from 'Available gases' then uses '>>>' to move it over to 'Chosen Components:' only the last column is user input. Then the calculate button orders them and then brings them down to 'Result:'. I want to be able to keep track of the mol% values entered for that specific component. – Drees May 01 '19 at 20:14
  • Looks similar to mine, but Mol% column should be blank and allow the user to input a value – Drees May 01 '19 at 21:02
  • @Drees Do you want the user to be able to edit the values of mol%? If so, do I need to be blank ?, I say it because I can set it as editable without needing to blank the value – eyllanesc May 01 '19 at 21:05
  • @ellyanesc Yes, it should be blank at first until user inputs value – Drees May 01 '19 at 21:07
  • @Drees One more doubt: If the %mol is modified, it will be recalculated automatically since if the value is very small compared to the other components it will be replaced by another row. Is this fine? – eyllanesc May 01 '19 at 21:08
  • @ellyanesc I think that would be fine, although i'd prefer for user to re-click button. – Drees May 01 '19 at 21:14
  • @ellyanesc The Mol% in the 'chosen gases' (top QTree) should be the one that is editable, not the bottom one. I have created my window using Qt Designer, so I have two separate QTreeWidgets, I can look more into your code and see if I can change it to fit mine somehow – Drees May 01 '19 at 22:14
  • @Drees So you want the QTreeView to have the items blank? – eyllanesc May 01 '19 at 22:20