fc2ブログ

ゼンリョクノート

いってみよう やってみよう のブログ

[PyQt] QTreeView で カスタム model と delegate を使った場合にソートが動作しない

 QTreeView で作った表をカラムでソートする時の話です。
以下のように普通に作成している場合は QTreeView の setSortingEnabled() を True にしていれば何の問題もなく普通にソートできます。


# -*- coding:utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore

DATA = [{'name': 'Taro', 'age': 20, 'color': [200, 100, 100]},
{'name': 'Akiyo', 'age': 28, 'color': [100, 200, 100]},
{'name': 'Masashi', 'age': 31, 'color': [100, 100, 200]},
{'name': 'Noriko', 'age': 17, 'color': [200, 200, 100]}]

class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('Tree View')

treeView = QtGui.QTreeView()
treeView.setSortingEnabled(True)

itemModel = QtGui.QStandardItemModel(0, 2)
itemModel.setHeaderData(0, QtCore.Qt.Horizontal, 'Name')
itemModel.setHeaderData(1, QtCore.Qt.Horizontal, 'Age')
itemModel.horizontalHeaderItem(0).setTextAlignment(QtCore.Qt.AlignCenter)
itemModel.horizontalHeaderItem(1).setTextAlignment(QtCore.Qt.AlignCenter)

idx = 0
for human_data in DATA:
for key, value in human_data.items():
if key == 'name':
name_item = QtGui.QStandardItem(value)
itemModel.setItem(idx, 0, name_item)
elif key == 'age':
age_item = QtGui.QStandardItem(str(value))
itemModel.setItem(idx, 1, age_item)
idx += 1

treeView.setModel(itemModel)

hbox = QtGui.QHBoxLayout()
hbox.addWidget(treeView)
self.setLayout(hbox)
self.resize(250, 150)
# end Example

def main():
app = QtGui.QApplication(sys.argv)
ui = Example()
ui.show()
sys.exit(app.exec_())

if __name__ == '__main__':
main()





 だけど delegate とカスタム model を使って自分の好きな形で view を表示させたい場合に問題は起こりました。
(model, view, delegate についてはまた別途まとめる予定です。)
要は以下のようなことをしようとした時です。


# -*- coding:utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore

DATA = [{'name': 'Taro', 'age': 20, 'color': [200, 100, 100]},
{'name': 'Akiyo', 'age': 28, 'color': [100, 200, 100]},
{'name': 'Masashi', 'age': 31, 'color': [100, 100, 200]},
{'name': 'Noriko', 'age': 17, 'color': [200, 200, 100]}]

# Custom Role
AGE_ROLE = QtCore.Qt.UserRole + 1

class CustomListDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent=None):
super(CustomListDelegate, self).__init__(parent)

def paint(self, painter, option, index):
if option.state & QtGui.QStyle.State_Selected:
bgBrush = QtGui.QBrush(QtGui.QColor(60, 60, 60))
bgPen = QtGui.QPen(QtGui.Color(60, 60, 60), 0.5, QtCore.Qt.SolidLine)
painter.setPen(bgPen)
painter.setBrush(bgBrush)
painter.drawRext(option.rect)

color = index.data(QtCore.Qt.ForegroundRole)

pen = QtGui.QPen(QtGui.QColor(color.toString()), 0.5, QtCore.Qt.SolidLine)
painter.setPen(pen)

if index.column() == 0:
name = index.data(QtCore.Qt.DisplayRole)
painter.drawText(option.rect,
QtCore.Qt.AlignVCenter|QtCore.Qt.AlignLeft,
name.toString())
elif index.column() == 1:
age = index.data(AGE_ROLE)
painter.drawText(option.rect,
QtCore.Qt.AlignVCenter|QtCore.Qt.AlignRight,
age.toString())

def sizeHint(self, option, index):
return QtCore.QSize(0, 40)

def setModelData(self, editor, model, index):
value = editor.text()
if index.column() == 0:
model.setData(index, value, QtCore.Qt.DisplayRole)
elif index.column() == 1:
model.setData(index, value, AGE_ROLE)
# end CustomListDelegate


class CustomListModel(QtGui.QStandardItemModel):
def __init__(self, parent=None, data=[]):
super(CustomListModel, self).__init__(parent)
self.items = data

idx = 0
for human_data in self.items:
for key, value in human_data.items():
if key == 'name':
name_item = QtGui.QStandardItem(value)
self.setItem(idx, 0, name_item)
elif key == 'age':
age_item = QtGui.QStandardItem(str(value))
self.setItem(idx, 1, age_item)
idx += 1

def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid() or not 0 <= index.row() < len(self.items):
return None

if role == QtCore.Qt.DisplayRole:
return self.items[index.row()].get('name')
elif role == AGE_ROLE:
return self.items[index.row()].get('age')
elif role == QtCore.Qt.ForegroundRole:
color = self.items[index.row()].get('color', [])
return QtGui.QColor(*color)

def setData(self, index, value, role=QtCore.Qt.EditRole):
if not index.isValid() or \
not 0 <= index.row() < len(self.items) or \
not value:
return False

if role == QtCore.Qt.DisplayRole:
self.items[index.row()]['name'] = str(value)
self.dataChanged.emit(index, index)
return True
elif role == AGE_ROLE:
self.items[index.row()]['age'] = value
self.dataChanged.emit(index, index)
return True
else:
return False
# end CustomListModel


class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('Tree View')

treeView = QtGui.QTreeView()
treeView.setSortingEnabled(True)

itemModel = CustomListModel(self, data=DATA)
itemModel.setHeaderData(0, QtCore.Qt.Horizontal, 'Name')
itemModel.setHeaderData(1, QtCore.Qt.Horizontal, 'Age')
itemModel.horizontalHeaderItem(0).setTextAlignment(QtCore.Qt.AlignCenter)
itemModel.horizontalHeaderItem(1).setTextAlignment(QtCore.Qt.AlignCenter)
treeView.setModel(itemModel)

listDelegate = CustomListDelegate()
treeView.setItemDelegate(listDelegate)

hbox = QtGui.QHBoxLayout()
hbox.addWidget(treeView)
self.setLayout(hbox)
self.resize(250, 230)
# end Example

def main():
app = QtGui.QApplication(sys.argv)
ui = Example()
ui.show()
sys.exit(app.exec_())

if __name__ == '__main__':
main()





 まぁ、今回はテストとして delegate とカスタム model を使って文字色とリストの幅を変更しています。
見た目は上手くいっているように見えますが、実際にはヘッダーをクリックした時のソートが上手く動作しません。
このような使い方をしている場合、どうやらカスタム model 内で sort() メソッドも上書きしてあげる必要があるようです。


# -*- coding:utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore

DATA = [{'name': 'Taro', 'age': 20, 'color': [200, 100, 100]},
{'name': 'Akiyo', 'age': 28, 'color': [100, 200, 100]},
{'name': 'Masashi', 'age': 31, 'color': [100, 100, 200]},
{'name': 'Noriko', 'age': 17, 'color': [200, 200, 100]}]

# Custom Role
AGE_ROLE = QtCore.Qt.UserRole + 1

class CustomListDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent=None):
super(CustomListDelegate, self).__init__(parent)

def paint(self, painter, option, index):
if option.state & QtGui.QStyle.State_Selected:
bgBrush = QtGui.QBrush(QtGui.QColor(60, 60, 60))
bgPen = QtGui.QPen(QtGui.Color(60, 60, 60), 0.5, QtCore.Qt.SolidLine)
painter.setPen(bgPen)
painter.setBrush(bgBrush)
painter.drawRext(option.rect)

color = index.data(QtCore.Qt.ForegroundRole)

pen = QtGui.QPen(QtGui.QColor(color.toString()), 0.5, QtCore.Qt.SolidLine)
painter.setPen(pen)

if index.column() == 0:
name = index.data(QtCore.Qt.DisplayRole)
painter.drawText(option.rect,
QtCore.Qt.AlignVCenter|QtCore.Qt.AlignLeft,
name.toString())
elif index.column() == 1:
age = index.data(AGE_ROLE)
painter.drawText(option.rect,
QtCore.Qt.AlignVCenter|QtCore.Qt.AlignRight,
age.toString())

def sizeHint(self, option, index):
return QtCore.QSize(0, 40)

def setModelData(self, editor, model, index):
value = editor.text()
if index.column() == 0:
model.setData(index, value, QtCore.Qt.DisplayRole)
elif index.column() == 1:
model.setData(index, value, AGE_ROLE)
# end CustomListDelegate


class CustomListModel(QtGui.QStandardItemModel):
def __init__(self, parent=None, data=[]):
super(CustomListModel, self).__init__(parent)
self.items = data

self.refreshItems()

def refreshItems(self):
idx = 0
for human_data in self.items:
for key, value in human_data.items():
if key == 'name':
name_item = QtGui.QStandardItem(value)
self.setItem(idx, 0, name_item)
elif key == 'age':
age_item = QtGui.QStandardItem(str(value))
self.setItem(idx, 1, age_item)
idx += 1

def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid() or not 0 <= index.row() < len(self.items):
return None

if role == QtCore.Qt.DisplayRole:
return self.items[index.row()].get('name')
elif role == AGE_ROLE:
return self.items[index.row()].get('age')
elif role == QtCore.Qt.ForegroundRole:
color = self.items[index.row()].get('color', [])
return QtGui.QColor(*color)

def setData(self, index, value, role=QtCore.Qt.EditRole):
if not index.isValid() or \
not 0 <= index.row() < len(self.items) or \
not value:
return False

if role == QtCore.Qt.DisplayRole:
self.items[index.row()]['name'] = str(value)
self.dataChanged.emit(index, index)
return True
elif role == AGE_ROLE:
self.items[index.row()]['age'] = value
self.dataChanged.emit(index, index)
return True
else:
return False

def sort(self, index, reverse):
if index == 0:
self.items = sorted(self.items, key=lambda x: x['name'], reverse=reverse)
elif index == 1:
self.items = sorted(self.items, key=lambda x: x['age'], reverse=reverse)

self.refreshItems()
# end CustomListModel


class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()

def initUI(self):
self.setWindowTitle('Tree View')

treeView = QtGui.QTreeView()
treeView.setSortingEnabled(True)

itemModel = CustomListModel(self, data=DATA)
itemModel.setHeaderData(0, QtCore.Qt.Horizontal, 'Name')
itemModel.setHeaderData(1, QtCore.Qt.Horizontal, 'Age')
itemModel.horizontalHeaderItem(0).setTextAlignment(QtCore.Qt.AlignCenter)
itemModel.horizontalHeaderItem(1).setTextAlignment(QtCore.Qt.AlignCenter)
treeView.setModel(itemModel)

listDelegate = CustomListDelegate()
treeView.setItemDelegate(listDelegate)

hbox = QtGui.QHBoxLayout()
hbox.addWidget(treeView)
self.setLayout(hbox)
self.resize(250, 230)
# end Example

def main():
app = QtGui.QApplication(sys.argv)
ui = Example()
ui.show()
sys.exit(app.exec_())

if __name__ == '__main__':
main()





これが 100% 正しいやり方かどうかは分かりませんがとりあえず動作しているので良しとします。笑


2015/10/19

[PyQt] QTreeView で カスタム model と delegate を使った場合にソートが動作しない

- Tags -

関連記事
スポンサーサイト



 PythonPyQt

0 Comments

Add your comment