In PyQt 5.11.2 (on Windows 10), QFileDialog with the DontUseNativeDialog option set has a bug when sorting by the 'Date Modified' column: it does not sort by the actual date - it sorts alphabetically by the non-zero-padded MM-DD-YYYY string, meaning that 9-12-2018 shows up as being more recent than 12-12-2018.
Is there a workaround or a fix?
The native dialog does sort by date correctly, but, the motivation for using the non-native dialog is that the native dialog does not respect fileDialog.setProxyModel (used to filter out certain files by more complex regex rules), described here and here, which I think is spelled out / alluded to by this line in the docs:
By default, a platform-native file dialog will be used if the platform has one. In that case, the widgets which would otherwise be used to construct the dialog will not be instantiated, so related accessors such as layout() and itemDelegate() will return null. You can set the DontUseNativeDialog option to ensure that the widget-based implementation will be used instead of the native dialog.
The code: the dialog is spawned by calling self.load() (no arguments)
def load(self,fileName=None):
if not fileName:
fileDialog=QFileDialog()
fileDialog.setOption(QFileDialog.DontUseNativeDialog)
fileDialog.setProxyModel(CSVFileSortFilterProxyModel(self))
fileDialog.setNameFilter("CSV Radio Log Data Files (*.csv)")
fileDialog.setDirectory(self.firstWorkingDir)
if fileDialog.exec_():
fileName=fileDialog.selectedFiles()[0]
... and the entire CSVFileSortFilterProxyModel class which only has one function:
# code for CSVFileSortFilterProxyModel partially taken from
# https://github.com/ZhuangLab/storm-control/blob/master/steve/qtRegexFileDialog.py
class CSVFileSortFilterProxyModel(QSortFilterProxyModel):
def __init__(self,parent=None):
# print("initializing CSVFileSortFilterProxyModel")
super(CSVFileSortFilterProxyModel,self).__init__(parent)
# filterAcceptsRow - return True if row should be included in the model, False otherwise
#
# do not list files named *_fleetsync.csv or *_clueLog.csv
# do a case-insensitive comparison just in case
def filterAcceptsRow(self,source_row,source_parent):
# print("CSV filterAcceptsRow called")
source_model=self.sourceModel()
index0=source_model.index(source_row,0,source_parent)
# Always show directories
if source_model.isDir(index0):
return True
# filter files
filename=source_model.fileName(index0).lower()
# filename=self.sourceModel().index(row,0,parent).data().lower()
# print("testing lowercased filename:"+filename)
# never show non- .csv files
if filename.count(".csv")<1:
return False
if filename.count("_fleetsync.csv")+filename.count("_cluelog.csv")==0:
return True
else:
return False
The problem is caused by the custom QSortFilterProxyModel since in the lessThan method the values are strings, the solution is to convert to the appropriate type for the comparison:
class CSVFileSortFilterProxyModel(QtCore.QSortFilterProxyModel):
def filterAcceptsRow(self,source_row,source_parent):
source_model = self.sourceModel()
index0=source_model.index(source_row,0,source_parent)
if source_model.isDir(index0):
return True
filename = source_model.fileName(index0).lower()
if filename.count(".csv")<1:
return False
return filename.count("_fleetsync.csv")+filename.count("_cluelog.csv") == 0
def lessThan(self, left, right):
source_model = self.sourceModel()
if left.column() == right.column() == 1:
if source_model.isDir(left) and not source_model.isDir(right):
return True
return source_model.size(left) < source_model.size(right)
if left.column() == right.column() == 2:
return source_model.type(left) < source_model.type(right)
if left.column() == right.column() == 3:
return source_model.lastModified(left) < source_model.lastModified(right)
return super(CSVFileSortFilterProxyModel, self).lessThan(left, right)
As indicated by @ekhumoro a simpler option is to overwrite the proxy sort:
class CSVFileSortFilterProxyModel(QtCore.QSortFilterProxyModel):
def filterAcceptsRow(self,source_row,source_parent):
source_model = self.sourceModel()
index0=source_model.index(source_row,0,source_parent)
if source_model.isDir(index0):
return True
filename = source_model.fileName(index0).lower()
if filename.count(".csv")<1:
return False
return filename.count("_fleetsync.csv")+filename.count("_cluelog.csv") == 0
def sort(self, column, order):
self.sourceModel().sort(column, order)