QStandardItemModel - 添加一行只有一个项目
QStandardItemModel - add a row with only one item
MRE:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5 import QtCore
import PyQt5
import sys, types
class OrgItemModel( QStandardItemModel ):
def __init__( self, tree_view ):
super().__init__()
self.tree_view = tree_view
self.setHorizontalHeaderLabels( [ 'Tasks', 'Due date', 'Notes' ] )
def configure( tree_view, main_window ):
tree_view.setModel( OrgItemModel( tree_view ) )
def load_from_list( self ):
new_model = OrgItemModel( self )
root_item = new_model.invisibleRootItem()
lines = [
'Europe ||| 2020-10-26 ||| some notes',
'==France ||| 2020-10-27 ||| some France notes',
'====Paris ||| --- ||| blah blah',
'==Germany ||| ||| ',
'==Italy ||| 2020-10-28 ||| some Italy notes',
'====Rome ||| ||| ',
'==Ireland ||| 2020-10-30 ||| blah blah',
]
new_model.ptr = 0
def read_child_rows( item, depth = 0 ):
while new_model.ptr < len( lines ):
line = lines[ new_model.ptr ]
new_model.ptr += 1
n_equals = len( line ) - len( line.lstrip('=') )
item_text = line.lstrip('=').strip()
read_depth = n_equals / 2
if read_depth == depth:
item_col0 = QStandardItem( item_text )
# split up the line by the substring ' ||| '
item_texts = line.split( ' ||| ' )
text_col0 = item_texts[ 0 ].lstrip( '=' ).strip()
text_col1 = item_texts[ 1 ].strip()
text_col2 = item_texts[ 2 ].strip()
item_col0 = QStandardItem( text_col0 )
if text_col1 == '':
print( f'col1 blank for {text_col0}')
item_col1 = None
else:
item_col1 = QStandardItem( text_col1 )
if text_col2 == '':
print( f'col2 blank for {text_col0}')
item_col2 = None
else:
item_col2 = QStandardItem( text_col2 )
if item_col1 == None or item_col2 == None:
print( 'inserting just first item...')
item.appendRow( item_col0 )
else:
item.appendRow( [ item_col0, item_col1, item_col2 ] )
# NB here I'm trying to examine the row which has just been added.
# But this is now not giving the right value for rowCount: this
# may be because previously I was experimenting with new_model.appendRow
# rather than item.appendRow: the central problem still arises...
print( f'row count {new_model.rowCount()}')
for j_col in range( 3 ):
index = new_model.index( new_model.rowCount() - 1, j_col )
data_at_coord = new_model.data( index )
print( data_at_coord, index.isValid() )
else:
new_model.ptr -= 1
if read_depth > depth:
read_child_rows( item_col0, depth + 1 )
else:
break
read_child_rows( root_item )
del new_model.ptr
self.setModel( new_model )
self.expandAll()
self.resizeColumnToContents( 0 )
tree_view.load_from_list = types.MethodType( load_from_list, tree_view )
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.resize(1100, 580)
self.centralwidget = QWidget(MainWindow)
self.treeView = QTreeView(self.centralwidget)
self.treeView.setGeometry(QtCore.QRect(20, 20, 1000, 320 ))
MainWindow.setCentralWidget(self.centralwidget)
class MyWindow( QMainWindow):
def __init__(self):
super( MyWindow, self ).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
configure( self.ui.treeView, self )
self.ui.treeView.load_from_list()
app = QApplication([])
application = MyWindow()
application.show()
sys.exit(app.exec())
我对 QStandardItemModel
方法 insertRow
和 appendRow
感到困惑。目前我在模型中有一个包含 3 列的 QTreeView
。我正在遍历一个列表来重建树,基于列表中的缩进(在每个深度级别的行的开头使用“==”)来表示树的深度。
列表中的每一行用字符串“|||”分隔给定行上三个项目的文本(我计划在某个时候实现JSON序列化)。
当我在“|||”上拆分行文本时,这会产生 3 个字符串。如果第二个或第三个是空白字符串,这应该意味着不应将此项添加到模型中,即在树视图中应该只看到树节点,而其行的其余部分不应包含任何项目(即无效索引).
...但我发现即使我只插入 1 个项目(而不是 3 个项目的列表),也会(有时)在 QTreeView
模型的行中创建三个项目。上面的检查行中的 index.isValid()
总是 returns True
,即使它只是插入了一项(在 col0 中)。在这种情况下,col1 和 col2 的 data_at_coord
打印为“None”。
因此,在显示的视图中,我可以单击已执行此操作的行的 col1 或 col2,然后找到一个空白的可编辑项目,并在检查时发现这些是有效的索引。他们不应该是:我的目的是添加一个新行,其中 col1 和 col2 中没有项目,只有 col0 中没有项目。
当我 运行 上面的 MRE 时,“罗马”行表现正常:您可以尝试单击 col1 和 col2,但没有任何反应。 但是“德国”行似乎表现得很奇怪:当我点击 col1 或 col2 时,我发现我正在编辑一个空白字符串。
注意,如果您实际上使用 new_model.appendRow
而不是 item.appendRow
,则结果会有所不同:
- 树构造算法没有按预期工作并且
- “罗马”行现在也无法执行“应该”执行的操作,并且 col1 和 col2 是可编辑的空白字符串。
问题不是由 appendRow 方法引起的,而是由未记录的行为引起的。似乎对于树型模型,所有节点总是具有相同数量的列,除了没有子节点的节点会产生这种意外行为。解决方法是在文本项中创建带有 Qt::NoItemFlags 标志的 QStandardItem,这样用户就无法与它们交互:
import sys
from PyQt5.QtCore import QRect, Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication, QMainWindow, QTreeView, QWidget
class OrgItemModel(QStandardItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self.setHorizontalHeaderLabels(["Tasks", "Due date", "Notes"])
def configure_from_list(model, lines):
def get_parent_item(depth):
parent = model.invisibleRootItem()
if depth == 0:
return parent
for _ in range(depth):
if parent.hasChildren():
parent = parent.child(parent.rowCount() - 1)
else:
it = QStandardItem()
it.setFlags(Qt.NoItemFlags)
parent.appendRow(it)
parent = it
return parent
for line in lines:
values = line.strip("=")
depth = (len(line) - len(values)) // 2
items = []
for value in values.split("|||"):
text = value.strip()
item = QStandardItem()
if text:
item.setText(text)
else:
item.setFlags(Qt.NoItemFlags)
items.append(item)
parent_item = get_parent_item(depth)
parent_item.appendRow(items)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.resize(1100, 580)
self.centralwidget = QWidget(MainWindow)
self.treeView = QTreeView(self.centralwidget)
self.treeView.setGeometry(QRect(20, 20, 1000, 320))
MainWindow.setCentralWidget(self.centralwidget)
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
lines = [
"Europe ||| 2020-10-26 ||| some notes",
"==France ||| 2020-10-27 ||| some France notes",
"====Paris ||| --- ||| blah blah",
"==Germany ||| ||| ",
"==Italy ||| 2020-10-28 ||| some Italy notes",
"====Rome ||| ||| ",
"==Ireland ||| 2020-10-30 ||| blah blah",
]
model = OrgItemModel()
self.ui.treeView.setModel(model)
configure_from_list(model, lines)
self.ui.treeView.expandAll()
app = QApplication(sys.argv)
application = MyWindow()
application.show()
sys.exit(app.exec())
MRE:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5 import QtCore
import PyQt5
import sys, types
class OrgItemModel( QStandardItemModel ):
def __init__( self, tree_view ):
super().__init__()
self.tree_view = tree_view
self.setHorizontalHeaderLabels( [ 'Tasks', 'Due date', 'Notes' ] )
def configure( tree_view, main_window ):
tree_view.setModel( OrgItemModel( tree_view ) )
def load_from_list( self ):
new_model = OrgItemModel( self )
root_item = new_model.invisibleRootItem()
lines = [
'Europe ||| 2020-10-26 ||| some notes',
'==France ||| 2020-10-27 ||| some France notes',
'====Paris ||| --- ||| blah blah',
'==Germany ||| ||| ',
'==Italy ||| 2020-10-28 ||| some Italy notes',
'====Rome ||| ||| ',
'==Ireland ||| 2020-10-30 ||| blah blah',
]
new_model.ptr = 0
def read_child_rows( item, depth = 0 ):
while new_model.ptr < len( lines ):
line = lines[ new_model.ptr ]
new_model.ptr += 1
n_equals = len( line ) - len( line.lstrip('=') )
item_text = line.lstrip('=').strip()
read_depth = n_equals / 2
if read_depth == depth:
item_col0 = QStandardItem( item_text )
# split up the line by the substring ' ||| '
item_texts = line.split( ' ||| ' )
text_col0 = item_texts[ 0 ].lstrip( '=' ).strip()
text_col1 = item_texts[ 1 ].strip()
text_col2 = item_texts[ 2 ].strip()
item_col0 = QStandardItem( text_col0 )
if text_col1 == '':
print( f'col1 blank for {text_col0}')
item_col1 = None
else:
item_col1 = QStandardItem( text_col1 )
if text_col2 == '':
print( f'col2 blank for {text_col0}')
item_col2 = None
else:
item_col2 = QStandardItem( text_col2 )
if item_col1 == None or item_col2 == None:
print( 'inserting just first item...')
item.appendRow( item_col0 )
else:
item.appendRow( [ item_col0, item_col1, item_col2 ] )
# NB here I'm trying to examine the row which has just been added.
# But this is now not giving the right value for rowCount: this
# may be because previously I was experimenting with new_model.appendRow
# rather than item.appendRow: the central problem still arises...
print( f'row count {new_model.rowCount()}')
for j_col in range( 3 ):
index = new_model.index( new_model.rowCount() - 1, j_col )
data_at_coord = new_model.data( index )
print( data_at_coord, index.isValid() )
else:
new_model.ptr -= 1
if read_depth > depth:
read_child_rows( item_col0, depth + 1 )
else:
break
read_child_rows( root_item )
del new_model.ptr
self.setModel( new_model )
self.expandAll()
self.resizeColumnToContents( 0 )
tree_view.load_from_list = types.MethodType( load_from_list, tree_view )
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.resize(1100, 580)
self.centralwidget = QWidget(MainWindow)
self.treeView = QTreeView(self.centralwidget)
self.treeView.setGeometry(QtCore.QRect(20, 20, 1000, 320 ))
MainWindow.setCentralWidget(self.centralwidget)
class MyWindow( QMainWindow):
def __init__(self):
super( MyWindow, self ).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
configure( self.ui.treeView, self )
self.ui.treeView.load_from_list()
app = QApplication([])
application = MyWindow()
application.show()
sys.exit(app.exec())
我对 QStandardItemModel
方法 insertRow
和 appendRow
感到困惑。目前我在模型中有一个包含 3 列的 QTreeView
。我正在遍历一个列表来重建树,基于列表中的缩进(在每个深度级别的行的开头使用“==”)来表示树的深度。
列表中的每一行用字符串“|||”分隔给定行上三个项目的文本(我计划在某个时候实现JSON序列化)。
当我在“|||”上拆分行文本时,这会产生 3 个字符串。如果第二个或第三个是空白字符串,这应该意味着不应将此项添加到模型中,即在树视图中应该只看到树节点,而其行的其余部分不应包含任何项目(即无效索引).
...但我发现即使我只插入 1 个项目(而不是 3 个项目的列表),也会(有时)在 QTreeView
模型的行中创建三个项目。上面的检查行中的 index.isValid()
总是 returns True
,即使它只是插入了一项(在 col0 中)。在这种情况下,col1 和 col2 的 data_at_coord
打印为“None”。
因此,在显示的视图中,我可以单击已执行此操作的行的 col1 或 col2,然后找到一个空白的可编辑项目,并在检查时发现这些是有效的索引。他们不应该是:我的目的是添加一个新行,其中 col1 和 col2 中没有项目,只有 col0 中没有项目。
当我 运行 上面的 MRE 时,“罗马”行表现正常:您可以尝试单击 col1 和 col2,但没有任何反应。 但是“德国”行似乎表现得很奇怪:当我点击 col1 或 col2 时,我发现我正在编辑一个空白字符串。
注意,如果您实际上使用 new_model.appendRow
而不是 item.appendRow
,则结果会有所不同:
- 树构造算法没有按预期工作并且
- “罗马”行现在也无法执行“应该”执行的操作,并且 col1 和 col2 是可编辑的空白字符串。
问题不是由 appendRow 方法引起的,而是由未记录的行为引起的。似乎对于树型模型,所有节点总是具有相同数量的列,除了没有子节点的节点会产生这种意外行为。解决方法是在文本项中创建带有 Qt::NoItemFlags 标志的 QStandardItem,这样用户就无法与它们交互:
import sys
from PyQt5.QtCore import QRect, Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication, QMainWindow, QTreeView, QWidget
class OrgItemModel(QStandardItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self.setHorizontalHeaderLabels(["Tasks", "Due date", "Notes"])
def configure_from_list(model, lines):
def get_parent_item(depth):
parent = model.invisibleRootItem()
if depth == 0:
return parent
for _ in range(depth):
if parent.hasChildren():
parent = parent.child(parent.rowCount() - 1)
else:
it = QStandardItem()
it.setFlags(Qt.NoItemFlags)
parent.appendRow(it)
parent = it
return parent
for line in lines:
values = line.strip("=")
depth = (len(line) - len(values)) // 2
items = []
for value in values.split("|||"):
text = value.strip()
item = QStandardItem()
if text:
item.setText(text)
else:
item.setFlags(Qt.NoItemFlags)
items.append(item)
parent_item = get_parent_item(depth)
parent_item.appendRow(items)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.resize(1100, 580)
self.centralwidget = QWidget(MainWindow)
self.treeView = QTreeView(self.centralwidget)
self.treeView.setGeometry(QRect(20, 20, 1000, 320))
MainWindow.setCentralWidget(self.centralwidget)
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
lines = [
"Europe ||| 2020-10-26 ||| some notes",
"==France ||| 2020-10-27 ||| some France notes",
"====Paris ||| --- ||| blah blah",
"==Germany ||| ||| ",
"==Italy ||| 2020-10-28 ||| some Italy notes",
"====Rome ||| ||| ",
"==Ireland ||| 2020-10-30 ||| blah blah",
]
model = OrgItemModel()
self.ui.treeView.setModel(model)
configure_from_list(model, lines)
self.ui.treeView.expandAll()
app = QApplication(sys.argv)
application = MyWindow()
application.show()
sys.exit(app.exec())