ItemChanged signal fires three times in QTreeWidget

874 views Asked by At

could somebody see what is causing the ItemChanged signal to fire three times instead of just once?

The intention is to:

  1. load data from an external source
  2. create a tree widget with the top level items corresponding to the columns of the data
  3. tick which column the user should see in a table
  4. create a table showing only the selected columns

I managed to fix the code so that it ignores the square selection (were only some of the children are selected) and to also ignore any changes to the child-items. The signal though still fires 3 times for every time a heading is either selected or dis-selected.

import pandas as pd 
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.Qt import Qt
import sys

df = pd.DataFrame()
column_names = ['something1', 'different1', 'nothing1']
df_2 = pd.DataFrame(columns = column_names)
df= df.append (df_2)
df.loc[len(df)] = [2, 4, 5]
df.loc[len(df)] = [6, 7, 8]
df.loc[len(df)] = [9, 10, 11]
df.loc[len(df)] = [12, 13, 14]

class My_test(QtWidgets.QFrame):
    def __init__ (self, df,*args, **kw):
        super(My_test, self).__init__(*args, **kw)

        # Import the datafrarme
        self.df = df

        self.horizontalLayout_2 = QtWidgets.QVBoxLayout()
        #Define the table
        self.table = QtWidgets.QTableWidget()
        #Define the tree
        self.tree = QtWidgets.QTreeWidget(self)
        self.tree.setHeaderLabel("Station Data")

        #Complete the Gui
        self.horizontalLayout_2.addWidget(self.table)
        self.horizontalLayout_2.addWidget(self.tree)
        self.setLayout(self.horizontalLayout_2)

        # Create the top level tree items (the same as the table headers)
        for name in df.columns:
            top_level_item_KD = QTreeWidgetItem([name])
            top_level_item_KD.setFlags(top_level_item_KD.flags() | Qt.ItemIsUserCheckable | Qt.ItemIsTristate )
            # self.tree.itemChanged['QTreeWidgetItem*','int'].connect(self.my_func)

            #Connect the itemChanged signal to the its slot
            self.tree.itemChanged.connect(self.return_checked_headers)

            #Populate each header with the appropriate data
            for data_point in df[name]:
                child_KD = QTreeWidgetItem([str(data_point)])
                child_KD.setFlags(child_KD.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
                child_KD.setCheckState(0, Qt.Unchecked)
                top_level_item_KD.addChild(child_KD)
            self.tree.addTopLevelItem(top_level_item_KD)

    def return_checked_headers(self,item ,column_index):
        #column_index is not used in the function. It is kept in the definition for a reminder of what is the second option of the "itemChanged" signal     
        my_selected_columns = []

        #Identify how many data columns have been generated
        top_level_count = self.tree.topLevelItemCount()

        #check if change is on a top level item
        if item.parent() == None:
            #ignore the change if it is a square (value of 1)
            if item.checkState(0)!=1:
                for index in  range(self.tree.topLevelItemCount()):         
                    tree_item = self.tree.topLevelItem(index)
                    if tree_item.checkState(0) !=0:
                        my_header = tree_item.text(0)
                        my_selected_columns.append(my_header)
                self.update_table(self.df,my_selected_columns, self.table)  

    def update_table(self, df, headers, table):
        print ('you are in the updating fucntion')
        self.df = df
        self.headers = headers
        self.table = table
        viewing_df = []
        viewing_df  =self.df[self.headers]

        column_count = len(self.headers)
        row_count = len(viewing_df)

        #Update row and column numbers to reflect input data
        self.table.setRowCount(len(viewing_df.index))
        self.table.setColumnCount(len(self.headers))

        #Create the column headings
        self.table.setHorizontalHeaderLabels(self.headers)
        #Transfer data
        for row_number, row_data in enumerate(viewing_df.values):
            for column_number, datum in enumerate(row_data):
                #Convert numerical data to 1 decimal place and ensure it is converted to a string               
                if type(datum)==float or int:
                    datum= str(round(datum,1))
                self.table.setItem(row_number, column_number,QTableWidgetItem(datum))

#Run the GUI        
app = QtWidgets.QApplication (sys.argv)
my_window = My_test(df)
my_window.show()

app.exec_()
1

There are 1 answers

1
musicamante On BEST ANSWER

That happens because you connected the signal for every cycle of for name in df.columns. Since you've three columns, the signal is connected three times, meaning that each time the data is changed (by checking the item), the function is called three times.

Just put the connection outside the for cycle and you'll have only one call.


    # ...
    self.setLayout(self.horizontalLayout_2)

    self.tree.itemChanged.connect(self.return_checked_headers)

    for name in df.columns:
        # ...