5

How can I sort a coloumn in pyqt by the highest number? Currently I have setSortingEnabled(True) and that only sorts it by the most numbers (ex. 1,1,1,1,2,2,2,3,3) i want to do it by the highest number for example (ex. 58,25,15,10). Thanks!

Data Update:

def setmydata(self):
    for n, key in enumerate(self.data):
        for m, item in enumerate(self.data[key]):
            newitem = QtGui.QTableWidgetItem(item)
            self.setItem(m, n, newitem)

Whole code:

import sys
from PyQt4.QtGui import QTableWidget 
from PyQt4 import QtGui,QtCore,Qt
import MySQLdb as mdb
from functools import partial
import time
class Window(QtGui.QDialog):
    process_column_signal = QtCore.pyqtSignal()
    def __init__(self,parent=None):
        super(Window, self).__init__()
        self.layout = QtGui.QVBoxLayout(self)
        self.db = mdb.connect('serv','user','pass','db')
        self.model = self.db.cursor()
        self.initialData = self.get_data_status()
        self.table1 = MyTableStatus(self.initialData, 145, 4)
        callback = partial(self.process_column,self.table1)
        self.process_column_signal.connect(callback)        
        self.layout.addWidget(self.table1)  
        self.timer_status = QtCore.QTimer()
        self.timer_status.timeout.connect(self.updateAllViews)
        self.timer_status.timeout.connect(self.some_method)
        # check every half-second
        self.timer_status.start(1000*5)
    def some_method(self):
        self.process_column_signal.emit()

    def get_data_status(self):
        self.model.execute("""SELECT cpu_juliet,cpu,cpu_julietleft FROM status
                              WHERE date = (SELECT MAX(date) FROM status)""")        
        rows_status_cpu = self.model.fetchone()
        self.listb1 = ['%s' % rows_status_cpu[0],'%s' % rows_status_cpu[2],'%s' % rows_status_cpu[1],'%s' % rows_status_cpu[1]]#['%s %s' % self.rows_status]
        self.model.execute("""SELECT disk_queue_juliet FROM status
                              WHERE date = (SELECT MAX(date) FROM status)""")        
        rows_status_disk_queue = self.model.fetchone()        
        self.lista1 = 'Juliet','Julietleft','Pong','Hulk'
        self.listc1 = ['%s' % rows_status_disk_queue,'%s' % rows_status_disk_queue,'%s' % rows_status_disk_queue,'%s' % rows_status_disk_queue ]
        if self.listb1[0] >= '80' or self.listc1[0] >= '9':
            server_status_Juliet = 'WARNING'
        else:
            server_status_Juliet = 'Normal'
        if self.listb1[1] >= '80' or self.listc1[1] >= '9':
            server_status_Julietleft = 'WARNING'
        else:
            server_status_Julietleft = 'Normal'
        if self.listb1[2] >= '80' or self.listc1[2] >= '9':
            server_status_Pong = 'WARNING'
        else:
            server_status_Pong = 'Normal'
        if self.listb1[3] >= '80' or self.listc1[3] >= '9':
            server_status_Hulk = 'WARNING'
        else:
            server_status_Hulk = 'Normal'
        self.listd1 = ['%s' % server_status_Juliet,'%s' % server_status_Julietleft,'%s' % server_status_Pong,'%s' % server_status_Hulk]
#        if server_status_Hulk == "WARNING": #or server_status_Pong == "WARNING" or server_status_Julietleft == "WARNING" or server_status_Juliet == "WARNING":
#            self.serverstatus.setStyleSheet("QTabWidget {color: red}")
        #status label conditions
        self.mystruct1 = {'A':self.lista1, 'B':self.listb1, 'C':self.listc1, 'D':self.listd1} 
        return self.mystruct1

    def updateAllViews(self):
        _ = self.get_data_status()
        self.updateTable()

    def updateTable(self):
        self.table1.updateFromDict(self.mystruct1)
    def process_column(table1, processCol=1):
        colCount = table1.table1.rowCount()
        for row in xrange(table1.table1.rowCount()):
            for col in xrange(4):
                try:
                    item = table1.table1.item(row, 3)
                    text = item.text()
                    if (float(text) >= 20.0 ):
                        for col in xrange(colCount):
                            print row
                            item = table1.table1.item(row,col)
                            item.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
                except:
                    pass


class MyTableStatus(QTableWidget):
    def __init__(self, thestruct, *args): 
        QTableWidget.__init__(self, *args)
        self.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Preferred)
        self.setHorizontalHeaderLabels(['Server', 'Avg. Disk Queue','CPU Load',"Status"])
        self.setSortingEnabled(False)


        self.data = {}
        self.setmydata()

    def updateFromDict(self, aDict):
        self.data.clear()
        self.data.update(aDict)

        self.setmydata()

    def setmydata(self):
        for n, key in enumerate(self.data):
            for m, item in enumerate(self.data[key]):
                newitem = QtGui.QTableWidgetItem(item)
                self.setItem(m, n, newitem)
def main():
   app = QtGui.QApplication(sys.argv)
   app.setStyle(QtGui.QStyleFactory.create("plastique"))
   main_window = Window()
   main_window.repaint()
   main_window.show() 
   sys.exit(app.exec_())

if __name__ == '__main__':
   main()

4 Answers4

14

Its sorting alpha-numerically (so, in terms of strings, '1', '10', '11', '12', '2', '20', '21', '22', '3', '4' etc. is the proper sort order. It appears that for a QTableWidgetItem, if you use the setData(Qt.EditRole, value) method, the sort order will work. Depending on your version of Qt (I assume) you may have to overload the less than method of your table widget item.

from PyQt4.QtCore import Qt, QVariant
from PyQt4.QtGui import QApplication, QTableWidget, QTableWidgetItem

class MyTableWidgetItem(QTableWidgetItem):
    def __lt__(self, other):
        if ( isinstance(other, QTableWidgetItem) ):
            my_value, my_ok = self.data(Qt.EditRole).toInt()
            other_value, other_ok = other.data(Qt.EditRole).toInt()

            if ( my_ok and other_ok ):
                return my_value < other_value

        return super(MyTableWidgetItem, self).__lt__(other)

if ( __name__ == '__main__' ):
    app = None
    if ( QApplication.instance() is None ):
        app = QApplication([])

    widget = QTableWidget()
    widget.setWindowFlags(Qt.Dialog)
    widget.setSortingEnabled(True)

    widget.setRowCount(50)
    widget.setColumnCount(3)
    for row in range(50):
       # create a normal QTableWidgetItem
       a = QTableWidgetItem()
       a.setText(str(row))
       widget.setItem(row, 0, a)

       # create a proper sorted item
       b = QTableWidgetItem()
       b.setData(Qt.EditRole, QVariant(row))
       widget.setItem(row, 1, b)

       # create a custom sorted item
       c = MyTableWidgetItem()
       c.setData(Qt.EditRole, QVariant(row))
       widget.setItem(row, 2, c)

    widget.show()
    if ( app ):
        app.exec_()
Eric Hulser
  • 3,912
  • 21
  • 20
  • What way are you hoping for? What do you have so far - how is it loading? The only part you need to really know is that instead of calling: item.setText(str(text)), you're calling item.setData(Qt.EditRole, QVariant(data)) – Eric Hulser Aug 13 '12 at 21:18
  • It is still sorting alphanumerically, when i put `item` as shown in the question, into the Qvariant. –  Aug 13 '12 at 21:25
  • 1
    I have edited the example - to be honest, I was surprised it worked in my test case. I have always had to overload the less than method before. Try this, use a subclass with the custom lt method and see if that works. – Eric Hulser Aug 13 '12 at 22:12
  • Strangely enough it still doesn't work, if I insert item(or in your case c) it stays the same, however when i plug in m in qvariant your case row, it just goes 1-169 in each coloumn (the number of rows used up), any ideas why? –  Aug 16 '12 at 18:12
  • Could you post your code? I think I've come as far as I can thinking about it abstractly...thanks! – Eric Hulser Aug 16 '12 at 23:14
  • 1
    So the important part of my example is the new QTableWidgetItem subclass that implements its own less than method. You're still using the standard QTableWidgetItem in your code. You need to use a custom class. – Eric Hulser Aug 17 '12 at 16:47
  • Just to note, even though we have already use `setData`, the argument itself must also in form of integer, not string. So, this won't make the sorting work : `setData(Qt.DisplayRole, '10')`, but this will work :`setData(Qt.DisplayRole, 10)` – swdev Jun 05 '14 at 07:55
4

An alternative to using a custom Item is to use a SortFilterProxy (which you probably should be doing anyway). In that case you can define a lessThan method that will be used for sorting. Here's mine:

class SortFilterProxyModel(QSortFilterProxyModel):

    def lessThan(self, left_index, right_index):

        left_var = left_index.data(Qt.EditRole)
        right_var = right_index.data(Qt.EditRole)

        try:
            return float(left_var) < float(right_var)
        except (ValueError, TypeError):
            pass
        return left_var < right_var

It uses the EditRole to get the data for sorting, so you can do whatever mapping for usability you want to do in the DisplayRole cases. It also just tries to do numerical sorting for values than can be converted to float, or native sorting otherwise. Not the most general approach, but seems to work well for me. YMMV.

DirkR
  • 488
  • 2
  • 9
  • How do we use this in QTableWidget? Any example? – swdev Jun 05 '14 at 07:56
  • 1
    Proxies are simple, the above is almost complete. Create an instance of it, use `setSourceModel()` on it and use it instead of your original model. For editing you also need to forward `startEditing()` and `stopEditing()` calls to your original model. That seems to work well for me so far. – DirkR Jun 18 '14 at 10:11
1

Another possible solution to sort numeric could be, to add a special attribute and compare it based on that value. Mybe that is a more java like way.

class MyTableWidgetItem(QTableWidgetItem):

def __init__(self, value):
    QTableWidgetItem.__init__(self)
    self.value = value

def __lt__(self, other):
    if isinstance(other, MyTableWidgetItem):
        return self.value < other.value

    return super(QTableWidgetItem, self).__lt__(other)
S. Weber
  • 53
  • 5
-2

A simpler way is to prepend a few blank spaces to your numeric text. The ascii value of ' ' is less than '0', so it will work as you want it to. Here is what I do:

element = str(num)                # convert to str
padded = ('     '+element)[-5:]   # make all elements the same length
item.setText(padded)
SoloPilot
  • 1,484
  • 20
  • 17