PDA

View Full Version : PyQt5 - QTableView



szotke
26th February 2021, 13:34
I have a ui designed in Qt that uses the QCalendarWidget, QTableView, and in the Python code uses QtSql.

Upon startup, I query a SQL database for a whole date range of the designed timeframe, and populate the QTabelView with that result.

I am able to select the records and return the value to through the signal/slot to the Python code and able to use the values within the code.

If a user selects a specific date, the code requeries SQL for that specific date and repopulates the QTableView. All that works fine.

The problem is that after any re-query, the QTableView signal/slot does not update if a user selects a value.

d_stranz
26th February 2021, 15:34
the QTableView signal/slot does not update if a user selects a value

And what do you mean by that? What signal, what slot? Selection in table views is usually notified by a QItemSelectionModel, not the table view.

szotke
26th February 2021, 15:58
And what do you mean by that? What signal, what slot? Selection in table views is usually notified by a QItemSelectionModel, not the table view.


In my python code, I do an initial SQL query and populate the QTabelView with the results.

At this point in time, a user can click on a record, and the value is returned to the Python code via:
self.TableViewList.selectionModel().selectionChang ed.connect(self.get_Details)

And the user can click on any record and it will connect and the get_Details will execute (only displays what was chosen by the user at this point).

Now, one of the features I've already developed, was that if the user selects a date via the QCalendarWidget and the subsequent python code:
self.Calendar.selectionChanged.connect(self.requer y)

In the code it will requery SQL with the selected date, and will populate the QTableView with the correct dataset.

BUT ... if the user were to click on a record, the connection back to the Python code does not trigger.

d_stranz
26th February 2021, 23:47
BUT ... if the user were to click on a record, the connection back to the Python code does not trigger.

What connection back to the Python code? If you have set up a slot to handle the selection model's selectionChanged() signal, then that slot should be called. If the user is just clicking on an item or moving using the up / down arrow keys, then the currentChanged() signal is emitted. Maybe this is what you are looking for.

szotke
27th February 2021, 14:04
As I indicated above, for the QCalendarWidget uses the code:

self.Calendar.selectionChanged.connect(self.requer y)

and this code works fine. I can scroll through and select a date and it works every time.
Now with the QTableView, I use the code

self.TableViewList.selectionModel().selectionChang ed.connect(self.get_Details)

This works with the original population of the QTableView, but once I repopulate with the new data from the requery due to the date change, it ceases to function, the selectionChanged.connect just doesn't trigger the get_Details any longer.

d_stranz
27th February 2021, 16:53
the selectionChanged.connect just doesn't trigger the get_Details any longer.

I don't think you understand how signals and slots work. A connection (via connect()) between a signal (selectionChanged()) amd a slot (get_Details()) does not result in an immediate function call to the slot code.

What a connect() means is this: "In the future, when this signal is emitted by the signalling object instance (selectionModel), call this slot (get_Details()) and pass that slot the arguments provided by the signal". (Which in this case are the QModelIndex indexes that have been selected and deselected).

You do not call connect() every time you want to retrieve the new selection and pass that to the slot. You generally call connect() once when you are creating the object instances you are connecting. Thereafter, you simply wait for your slot to be called whenever the signalling object changes state.

You haven't posted enough of your code to verify what you are actually doing, but your description of how you are invoking connect() makes it seem like you have a misunderstanding of the protocol.

szotke
27th February 2021, 17:26
The bulk of the code is as follows. I have not included the SQL connection string as this works.


import sys
import datetime
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
from PyQt5 import QtSql as qts
from PyQt5 import uic
from PyQt5.QtSql import QSqlDatabase

MainWindow_Ui, MainWindow_Base = uic.loadUiType('CriticalAlarmListGUI.ui')



#class MainWindow(qtw.QMainWindow, Ui_MainWindow):
class MainWindow(MainWindow_Base, MainWindow_Ui):
events = {}

def __init__(self):
"""MainWindow constructor.

This widget will be the main window.
Define all the UI components in here.
"""
super().__init__()
# Main UI code goes here
self.setupUi(self)

# Connect to databases
#removed for security purposes


# Build the Query
StartDate = datetime.date(2020, 11, 1)
EndDate = datetime.date.today()

# Set the valid date range for the Calendar
self.Calendar.setDateRange(StartDate, EndDate)

# Format Dates for SQL
StartDate = "'" + str(StartDate.strftime("%m/%d/%Y")) + "'"
EndDate = "'" + str(EndDate.strftime("%m/%d/%Y")) + "'"

# Get the list of critical alarms within the date range
CriticalAlarms = self.get_Critical_Alarms(StartDate, EndDate)

# Loop through the Critical Alarm list and update the calendar date format of the results
for i in range(CriticalAlarms.rowCount()):
datetime_str = CriticalAlarms.record(i).value("EventStamp")
datetime_obj = datetime.datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S.%f0')

# Update the format of the date on the calendar day that the alarm occured
format = qtg.QTextCharFormat()
format.setFont(qtg.QFont('Times', 15))
date = qtc.QDate(datetime_obj)
self.Calendar.setDateTextFormat(date, format)

# Single Date Selected on the Calendar
self.Calendar.selectionChanged.connect(self.config _Critical_Alarms)

# Single record selected on the CriticalAlarmList widget
self.CriticalAlarmList.selectionModel().selectionC hanged.connect(self.get_Critical_Alarm_Details)

# End main UI code
self.show()


def config_Critical_Alarms(self):
print ("config_Critical_Alarms")
CriticalAlarms = None
StartDate = self.Calendar.selectedDate()
EndDate = StartDate.addDays(1)

StartDate = "'" + qtc.QDate.toString(StartDate, 'MM/dd/yyyy') + "'"
EndDate = "'" + qtc.QDate.toString(EndDate, 'MM/dd/yyyy') + "'"
CriticalAlarms = self.get_Critical_Alarms(StartDate, EndDate)


def get_Critical_Alarms(self, StartDate, EndDate):
print ("get_Critical_Alarms")

alarm_state = "'UNACK_ALM'"
queryString = f'SELECT EventStamp, TagName, Description, Priority FROM v_AlarmHistory WHERE (AlarmState = {alarm_state}) '\
f'AND (EventStamp >= {StartDate}) AND (Priority <= 10) AND '\
f'(EventStamp <= {EndDate}) ORDER BY EventStamp DESC'

# Process the SQL Query
CriticalAlarms = qts.QSqlQueryModel()
CriticalAlarms.setQuery(queryString)

self.CriticalAlarmList.setModel(CriticalAlarms)
self.CriticalAlarmList.resizeColumnsToContents()
self.StatusLabel.setText('Found {0} critical alarms between {1} and {2}'.format(CriticalAlarms.rowCount(), StartDate, EndDate))

return(CriticalAlarms)


def get_Critical_Alarm_Details(self, selected):
print ("get_Critical_Alarm_Details")
indexes = self.CriticalAlarmList.selectionModel().selectedRo ws()
model = self.CriticalAlarmList.model()
for index in indexes:
self.StatusLabel.setText('Selected row: {0}, Tag: {1}'.format(index.row(), model.data(model.index(index.row(), 1))))


def UiComponents(self, datetime_obj):
print ("UiComponents")
# Calendar day format
format = qtg.QTextCharFormat()
format.setFont(qtg.QFont('Times', 15))
date = qtc.QDate(datetime_obj)
value = self.Calendar.dateTextFormat()
self.label.setWordWrap(True)
self.label.setText("Date: " + str(value))




# Main
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
# Requirement to save a reference to MainWindow
# if it goes out of scope, it will be destroyed.
mw = MainWindow()
sys.exit(app.exec())





I believe I have the signals and slots set up correctly. Please make any suggestions to why I do not get the continued function of the signal.

d_stranz
27th February 2021, 19:40
OK, I am not a Python expert, but what I think is happening is that in get_Critical_Alarms() you are setting a new model on your list. This causes the selectionModel() to become invalid, and therefore the selectionChanged() signal is disconnected.

Try adding the connect() statement after line 90.

szotke
27th February 2021, 21:03
Try adding the connect() statement after line 90.


That worked.

Thank you very much.

d_stranz
27th February 2021, 21:44
Sure. I think your original idea would work if you did not create a new CriticalAlarms model every time you make a query. If you make CriticalAlarms a member variable of your class, create it and connect to the selectionChanged() signal and then simply call setQuery() on that instance each time the calendar selection changes, your model will be reset with each query and the current selection will be cleared. The selection model won't be destroyed, it will just be cleared. The next time an item is selected, the selectionChanged() signal will be emitted.

So basically, remove line 87 above, remove the new connection you added after line 90 (keeping the original one from line 61), and make CriticalAlarms into self.CriticalAlarms. Then I think your original code will work. You probably don't have to change get_CriticalAlarms() return value since you'll be returning the same object reference (self.CriticalAlarms).