Saturday 20 May 2023

Select QStandardItem inside a QTreeView based on another QTreeView's QStandardItem's visual position

i have a lot of data that i represent as QStandardItems inside two QTreeViews. I want to retrieve the visual position of an item inside the left tree in order to align the selection of it with the item inside the right tree. The user should be able to double-click on the desired item inside the left tree, afterwards which the right tree should represent the item corresponding to his selection. So far im using a function with the doubleClicked signal of the left tree as starting point for the logic.

self.tree_view_left.doubleClicked.connect(self.double_clicked)

Inside of double_clicked im retrieving the current index of the left tree and then im searching for his equivalent inside the right tree (NOTE i want to match only roots - not child elements).

    def double_clicked(self):
        index = self.selection_model.currentIndex()
        tree_left = self.tree_view_left
        tree_right = self.tree_view_right
        if index.isValid():
            item = index.model().itemFromIndex(index)
            if item.hasChildren():
                item_search = tree_right .model().findItems(item.text(), 
                Qt.MatchRecursive)
            # TODO: Find/Calc visual position of StandardItem inside TreeView
            left_vis_item = tree_left.indexAt(tree_left.rect().topLeft())
            right_vis_item =  tree_left.indexAt(tree_left.rect().bottomRight())
            # TODO: Move vertical scrollbar of right TreeView based on that visual position
            # Change right slider position
            tree_right.scrollTo(item_search[0].index())
            tree_right.selectionModel().select(item_search[0].index(), QItemSelectionModel.Select)

I have found similiar issues in the forum here, here and here, but either they had a used a different widget (QTreeWidget) or they used a function which i cannot implement (QAbstractItemView::visualRect. My approach was at first to retrieve the visual position by finding out the visible indices (left_vis_item, right_vis_item), but then i thought of using the mouse position. The last one is new to me. Any ideas?

EDIT: I have now added a MRE with a bare minimum of data from a mockup created with this website. The MainWindow Class, which holds (here minimum) main features, looks like this:

class MainWindow(QMainWindow):
def __init__(self):
    super().__init__()
    self.setWindowTitle("Example")
    window_geometry = self.frameGeometry()
    center_screen = QDesktopWidget().availableGeometry().center()
    window_geometry.moveCenter(center_screen)
    self.move(window_geometry.topLeft())
    group_box_left = QGroupBox('1. Tree view')
    group_box_right = QGroupBox('2. Tree view')
    self.tree_view_left = QTreeView(self)
    self.tree_view_left.setEditTriggers(QAbstractItemView.NoEditTriggers)
    tree_view_left_header = self.tree_view_left.header()
    tree_view_left_header.setDefaultSectionSize(200)
    tree_model_left = QStandardItemModel()
    self.tree_view_right = QTreeView(self)
    tree_model_right = QStandardItemModel()
    # Add data
    with open("mockup.json", 'r') as f:
        data = json.load(f)
    self.import_data(data, tree_model_left)
    self.import_data(data, tree_model_right)
    # Use filled model for tree view
    self.tree_view_left.setModel(tree_model_left)
    self.tree_view_left.setVisible(True)
    self.tree_view_left.setExpandsOnDoubleClick(False)
    self.tree_view_right.setModel(tree_model_right)
    self.selection_model = self.tree_view_left.selectionModel()
    self.tree_view_left.doubleClicked.connect(self.double_clicked)
    # Layout
    hor_box_left = QVBoxLayout()
    hor_box_left.addWidget(self.tree_view_left)
    group_box_left.setLayout(hor_box_left)
    hor_box_right = QVBoxLayout()
    hor_box_right.addWidget(self.tree_view_right)
    group_box_right.setLayout(hor_box_right)
    main_layout = QHBoxLayout()
    main_layout.addWidget(group_box_left)
    main_layout.addWidget(group_box_right)
    main_layout.setContentsMargins(100, 0, 100, 0)
    main_layout.setSpacing(50)
    widget = QWidget()
    widget.setLayout(main_layout)
    self.setCentralWidget(widget)

And the function for data import is:

def import_data(self, data, model):
    for i in range(0, len(data)):
        root = QStandardItem(str(i))
        root.appendRows([QStandardItem(data[i]["Firstname"]), QStandardItem(data[i]["Lastname"]),
                         QStandardItem(data[i]["City"])])
        model.appendRow(root)

I will explain the issue with snapshots. In the first snapshot you can see that there is an offset between the selected item in the left Tree and the right.Offset explained. The desired behaviour would be to remove this offset. The current implementation works with no offset ONLY if the items are collapsed (which will not always be the case) Without offset. I will also provide the used data for this example below:

[
{
    "Firstname": "Dotty",
    "Lastname": "O'Neill",
    "City": "Tucson"
},
{
    "Firstname": "Libbie",
    "Lastname": "Shuler",
    "City": "Surat Thani"
},
{
    "Firstname": "Caressa",
    "Lastname": "Henebry",
    "City": "Tucson"
},
{
    "Firstname": "Larine",
    "Lastname": "Schalles",
    "City": "Gdańsk"
},
{
    "Firstname": "Maryellen",
    "Lastname": "Bord",
    "City": "Palembang"
},
{
    "Firstname": "Genovera",
    "Lastname": "Desai",
    "City": "Basra"
},
{
    "Firstname": "Fina",
    "Lastname": "Thad",
    "City": "Mecca"
},
{
    "Firstname": "Emylee",
    "Lastname": "Flita",
    "City": "Vancouver"
},
{
    "Firstname": "Aurore",
    "Lastname": "Lipson",
    "City": "Makati City"
},
{
    "Firstname": "Florie",
    "Lastname": "Hachmin",
    "City": "Douala"
},
{
    "Firstname": "Brooks",
    "Lastname": "Grayce",
    "City": "Birmingham"
},
{
    "Firstname": "Kristan",
    "Lastname": "Allina",
    "City": "Bahía Blanca"
},
{
    "Firstname": "Dorothy",
    "Lastname": "Remmer",
    "City": "Nouméa"
},
{
    "Firstname": "Kittie",
    "Lastname": "Kussell",
    "City": "Detroit"
},
{
    "Firstname": "Lexine",
    "Lastname": "Letsou",
    "City": "Palikir"
},
{
    "Firstname": "Madelle",
    "Lastname": "Joseph",
    "City": "Mwanza"
},
{
    "Firstname": "Olivette",
    "Lastname": "Faust",
    "City": "Pelotas"
},
{
    "Firstname": "Damaris",
    "Lastname": "Slifka",
    "City": "Graz"
},
{
    "Firstname": "Dennie",
    "Lastname": "Merat",
    "City": "Abuja"
},
{
    "Firstname": "Augustine",
    "Lastname": "Burnside",
    "City": "Arbil"
}
]


from Select QStandardItem inside a QTreeView based on another QTreeView's QStandardItem's visual position

No comments:

Post a Comment