【问题标题】:QStandardItemModel - add a row with only one itemQStandardItemModel - 添加一行只有一个项目
【发布时间】:2020-10-26 18:51:49
【问题描述】:

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())

我对@9​​87654323@ 方法insertRowappendRow 感到困惑。目前我有一个QTreeView,模型中有 3 列。我正在遍历一个列表以重构树,基于列表中的缩进(在每个深度级别的行开头使用“==”来表示树深度。

列表中的每一行用字符串“|||”分隔给定行上三个项目的文本(我计划在某个时候实现 JSON 序列化)。

当我在“|||”上分割行文本时,这会产生 3 个字符串。如果第二个或第三个是空白字符串,这应该意味着这个项目不应该被添加到模型中,即在树视图中应该只看到树节点,并且它的行的其余部分应该不包含任何项目(即无效索引)。 ...但我发现即使我只插入 1 个项目(而不是 3 个项目的列表),也(有时)在 QTreeView 模型的行中创建了三个项目。并且上面检查行中的index.isValid() 总是返回True,即使它只是插入了一项(在col0 中)。在这种情况下,col1 和 col2 的data_at_coord 打印为“无”。

因此,在显示的视图中,我可以在我执行此操作的行中单击 col1 或 col2,并找到一个空白的可编辑项,并在检查时发现这些是有效的索引。它们不应该是:我的意图是添加一个新行,其中 col1 和 col2 中没有项目,只有 col0 中。

当我运行上述 MRE 时,“Rome”行表现正常:您可以尝试单击 col1 和 col2,但没有任何反应。 但“德国”行似乎表现得很奇怪:当我点击 col1 或 col2 时,我发现我正在编辑一个空白字符串。

注意,如果实际上您使用 new_model.appendRow 而不是 item.appendRow,那么结果会有所不同:

  1. 树构造算法无法正常工作,并且
  2. “Rome”行现在也无法执行“应该”执行的操作,并且 col1 和 col2 是可编辑的空白字符串。

【问题讨论】:

  • 我明白,但正如我所说,它会相当庞大。正在努力……(我是 PyQt5 新手,所以可能需要一些时间才能将事情缩减到可管理的大小……)
  • 我不明白为什么有网站经验的用户(如他们的 8k 所示)希望在明确需要 MRE 的情况下要求该类型的帖子。在提出问题之前,您应该花时间构建 MRE。
  • Python 人可能比 Java 世界更系统地期望 MRE。我最初希望这样的问题是一个已知问题,并且有人可以识别它。我发现 QTreeView 的 Python 和 C++ 文档非常不完整,很多知识可能只存在于用户的脑海中。
  • 这里的语言或他们周围的子社区不感兴趣,因为他们的问题可以概括为:我想做X,尝试做Y但它不起作用,我该如何解决?,然后另一方面,我们问自己:在 Y 中什么会失败?好吧,我们将执行 Y 来分析和理解错误,但是在帖子中我们无法看到如何重现错误,因为没有 MRE。

标签: python pyqt5 qtreeview


【解决方案1】:

问题不是由 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())

【讨论】:

  • 感谢您抽出宝贵时间对此进行分析。我发现像 PyQt5 这样的成熟项目会包含这种未记录的行为,这让我感到非常惊讶,在此之前似乎没有人注意到这一点,但显然我是 PyQt5 的新手,因此无法判断这些问题。
猜你喜欢
  • 2016-07-03
  • 1970-01-01
  • 2015-01-16
  • 1970-01-01
  • 1970-01-01
  • 2013-06-02
  • 2017-06-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多