I have a simple PyQt4 program that takes in 3 Particulars (Name,Gender & Address) whenever i clicked on OK button and save it as a binary file (3 particulars are hard coded in program for testing purpose). Then later will load that information back and display it in QTableWidget.
This is the layout of my program:
It has 2 scripts: DContainer.py and Data_Main.py
Dcontainer.py
import bisect
from PyQt4 import QtCore
class Person(object):
def __init__(self, Name = None, Gender = None , Address = None ):
self.Name = Name
self.Gender = Gender
self.Address = Address
class PersonContainer(object):
def __init__(self):
self.__fname = QtCore.QString("mydatabase.mqb")
self.__persons = []
self.__personFromId = {}
def __iter__(self):
for pair in iter(self.__persons):
yield pair[1]
def __len__(self):
return len(self.__persons)
def Clear(self):
self.__persons = []
self.__personFromId ={}
def add(self,person):
if id(person)in self.__personFromId:
return False
key = person.Name
bisect.insort_left(self.__persons, [key,person])
self.__personFromId[id(person)] = person
return True
def save(self):
fh = QtCore.QFile(self.__fname)
if not fh.open(QtCore.QIODevice.WriteOnly):
raise IOError , unicode(fh.errorString())
stream = QtCore.QDataStream(fh)
for key, person in self.__persons:
stream << person.Name << person.Gender << person.Address
def load(self):
fh = QtCore.QFile(self.__fname)
if not fh.open(QtCore.QIODevice.ReadOnly):
raise IOError , unicode(fh.errorString())
stream = QtCore.QDataStream(fh)
while not stream.atEnd():
Name = QtCore.QString()
Gender = QtCore.QString()
Address = QtCore.QString()
stream >> Name >> Gender >> Address
self.add(Person(Name,Gender,Address))
Data_Main.py
import sys
from PyQt4 import QtCore,QtGui
import DContainer
class MainDialog(QtGui.QDialog):
def __init__(self, parent = None):
super(MainDialog,self).__init__(parent)
self.InitGui()
self.persons = DContainer.PersonContainer()
self.Update()
def InitGui(self):
buttonbox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel)
self.table = QtGui.QTableWidget()
layout = QtGui.QVBoxLayout()
layout.addWidget(self.table)
layout.addWidget(buttonbox)
self.setLayout(layout)
self.connect(buttonbox.button(buttonbox.Ok), QtCore.SIGNAL("clicked()"),self.OK)
def OK(self):
NewPerson = DContainer.Person(QtCore.QString('This is another test'),QtCore.QString('Male'),QtCore.QString('Strand Road'))
self.persons.add(NewPerson)
self.persons.save()
self.Update()
def Update(self):
self.table.clear()
self.persons.load()
self.table.setRowCount(len(self.persons))
self.table.setColumnCount(3)
for row,person in enumerate(self.persons):
item = QtGui.QTableWidgetItem(person.Name)
self.table.setItem(row,0,item)
def Main():
app = QtGui.QApplication(sys.argv)
dialog = MainDialog()
dialog.show()
app.exec_()
if __name__ == "__main__":
Main()
My Problem is whenever i clicked on OK button, it create multiple table entries
After second click
It should not create multiple table entries as i have used
if id(person)in self.__personFromId:
return False
in my Add method in Dcontainer.py.
Rightfully, it should only show one item in the table unless i give the new person object with different name.
What is causing the problem?
The PersonContainer.add method is called twice when you click the OK button:
Directly from the MainDialog.OK method
Indirectly from the MainDialog.Update method, with self.persons.load()
You can add an optional argument to the Update method to trigger the call to load:
def Update(self, load=False):
self.table.clear()
if load:
self.persons.load()
And call this method with load set to True in the __init__ method:
def __init__(self, parent = None):
super(MainDialog,self).__init__(parent)
self.InitGui()
self.persons = DContainer.PersonContainer()
self.Update(True)
By the way, the old style signal/slot is no longer supported with PyQt5. This is how to write in the new style:
buttonbox.accepted.connect(self.OK)
buttonbox.rejected.connect(self.reject)
Related
In order to filter a TreeView with two levels (see image below), I used the class QSortFilterProxyModel which has a QStandardItemModel as source model.
to ignore the filtering of the root nodes, I overloaded the filterAcceptsRow() method as follows.
this is my main project working with copy/paste a code below model class, i replaced data import from database with a dummy data dictionary
import sys
from PyQt5 import QtWidgets,QtGui
from PyQt5.QtCore import QSortFilterProxyModel
services =[{'id':1,'chanel_name':'France 24','satId':0},
{'id':2,'chanel_name':'Eurosport 02','satId':0},
{'id':3,'chanel_name':'CANAL +','satId':0},
{'id':4,'chanel_name':'Eurosport 02','satId':0},
{'id':5,'chanel_name':'TV 5','satId':0},
{'id':6,'chanel_name':'BBC World','satId':0},
{'id':7,'chanel_name':'M6','satId':0},
{'id':15,'chanel_name':'TEVA','satId':0},
{'id':8,'chanel_name':'PLANETTE','satId':1},
{'id':9,'chanel_name':'France 2','satId':1},
{'id':10,'chanel_name':'France 3','satId':1},
{'id':11,'chanel_name':'MBC 1','satId':1},
{'id':12,'chanel_name':'NIL TV','satId':1},
{'id':13,'chanel_name':'M2','satId':1},
{'id':14,'chanel_name':'CANAL 02','satId':1},
{'id':15,'chanel_name':'CHANEL 1','satId':2},
{'id':8,'chanel_name':'CINE+ FAMILY','satId':2},
{'id':9,'chanel_name':'OCS CITY','satId':2},
{'id':10,'chanel_name':'OCS CHOCS','satId':2},
{'id':11,'chanel_name':'ACTION','satId':2},
{'id':12,'chanel_name':'NATIONAL GEO','satId':2},
{'id':13,'chanel_name':'TVEA','satId':2},
{'id':14,'chanel_name':'CANAL 02','satId':2},
]
class ProxyServiceBySatModel(QSortFilterProxyModel):
def __init__(self,):
super().__init__()
def filterAcceptsRow(self, source_row, source_parent):
index = self.sourceModel().index(source_row,0,source_parent)
if not source_parent.isValid():
return True
else:
return QSortFilterProxyModel.filterAcceptsRow(self,source_row, source_parent)
class ServicesBySat(QtGui.QStandardItemModel):
def __init__(self):
super(ServicesBySat,self).__init__()
self.setHorizontalHeaderLabels(['Sat name'])
self.satellite = ['ASTRA 19° E','HotBird 13°','NILSAT 7° W']
def importData(self, root=None):
sats= {}
root = self.invisibleRootItem()
for service in services:
sat = service['satId']
if sat not in sats:
parent = root
parent.appendRow(QtGui.QStandardItem(self.satellite[sat]))
sats[sat] = parent.child(parent.rowCount() - 1)
else:
parent = sats[sat]
parent.appendRow(QtGui.QStandardItem(service['chanel_name']))
class MainWindowISE(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('Tree search')
self.setGeometry(100,100,300,300)
self.setupUi()
self.model = ServicesBySat()
self.model.importData()
self.proxyModel = ProxyServiceBySatModel()
self.proxyModel.setSourceModel(self.model)
self.proxyModel.setFilterKeyColumn(-1)
self.treeView_services.setModel(self.proxyModel)
self.lineEdit_Search.textEdited.connect(self.on_lineEditSearch_textEdited)
def setupUi(self):
self.gridLayout = QtWidgets.QGridLayout(self)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.label = QtWidgets.QLabel("Search : ", self)
self.horizontalLayout.addWidget(self.label)
self.lineEdit_Search = QtWidgets.QLineEdit(self)
self.horizontalLayout.addWidget(self.lineEdit_Search)
self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)
self.treeView_services = QtWidgets.QTreeView(self)
self.gridLayout.addWidget(self.treeView_services, 1, 0, 1, 1)
def on_lineEditSearch_textEdited(self):
self.proxyModel.setFilterRegularExpression(self.lineEdit_Search.text())
def main():
app = QtWidgets.QApplication(sys.argv)
window =MainWindowISE()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
the filtering works well, but now to improve the behavior of my filter, i want to hide the root nodes that have not a childrens in filtered items by adding a condition like this considering that self is my proxy model.
For example,as shown in the representation below, i don't want the NILESAT element be displayed since it has no children who match the filter pattern
I modified my method as follows, to achieve the expected result but without success.
class ProxyServiceBySatModel(QSortFilterProxyModel):
def init(self,):
super().init()
def filterAcceptsRow(self, source_row, source_parent):
index = self.sourceModel().index(source_row,0,source_parent)
if not source_parent.isValid() and self.hasChildren(index) :
return True
else:
return QSortFilterProxyModel.filterAcceptsRow(self,source_row, source_parent)
or i can use this condition but a get the same result.
if not source_parent.isValid() and self.rowCount(index) > 0 :
but i get an exeption
QSortFilterProxyModel: index from wrong model passed to mapToSource
how is it possible to get the current row Count or hasChildren value inner filterAcceptsRow method, or another way to achieve the expected result.
I had two classes - main and sub interface
There is a pushbutton which will calls out the sub interface and I am trying to get the output of the sub tool interface directly (or almost immediately) so that it can be use within the push button function.
In my code, if I did the following:
hit on 'Click Me'
checked 2 options and hit the 'Apply to selected item' in the sub interface
the print line of 'my dict values' is still empty
Unless I create another function get_results in which then self.my_dict will be shown correctly.
As such, how can I code it in a way that once the 'Apply...' button is hit, self.my_dict will be updated without the need of creating another function? Or am I just overthinking things?
class SubMenuWindow(QtGui.QWidget):
def __init__(self, menu_items, parent=None):
super(SubMenuWindow, self).__init__(parent)
self.my_lyt = QtGui.QVBoxLayout()
self.checked_options = []
self.sel = {}
for menu_name, submenu_name in menu_items.items():
# Set the main menu item name
self.groupbox = QtGui.QGroupBox(self)
self.groupbox.setTitle(menu_name)
self.groupbox.setLayout(QtGui.QVBoxLayout())
self.my_lyt.addWidget(self.groupbox)
if submenu_name:
sub_txt = [action for action in submenu_name]
for s in sub_txt:
sub_chk = QtGui.QCheckBox(s)
self.checked_options.append(sub_chk)
self.groupbox.layout().addWidget(sub_chk)
apply_tag_btn = QtGui.QPushButton('Apply to selected item')
apply_tag_btn.clicked.connect(self.get_checked_options)
self.my_lyt.addWidget(apply_tag_btn)
self.my_lyt.addStretch()
self.setLayout(self.my_lyt)
self.show()
def get_checked_options(self):
for f in self.checked_options:
if f.isChecked():
self.sel[f.parent().title()] = f.text()
class MainWin(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWin, self).__init__(parent)
self.my_dict = {}
btnA = QtGui.QPushButton('Click Me')
btnA.clicked.connect(self.get_options)
btnB = QtGui.QPushButton('Get results')
btnB.clicked.connect(self.get_results)
layout = QtGui.QVBoxLayout()
layout.addWidget(btnA)
layout.addWidget(btnB)
self.setLayout(layout)
def get_options(self):
sample_dict = {'GrpA' : ['John', 'Zack'], 'GrpB' : ['Alice', 'Phan']}
self.subWin = SubMenuWindow(sample_dict)
# I had want to get the values from subWin as soon as User has hit on
# the 'Apply to selected item' button
self.my_dict = self.subWin.sel
print ">>> my dict values : ", self.my_dict
# do something else from here thereafter...
def get_results(self):
print self.subWin.sel
Creating the new window will not block, so your print statements will be executed before the user has anything selected. You could pass in a callback to notify the calling widget when the user changes the selection.
for example:
class SubMenuWindow(QtWidgets.QWidget):
def __init__(self, menu_items, parent=None, callback=None):
super(SubMenuWindow, self).__init__(parent)
self.callback = callback
[...]
def get_checked_options(self):
for f in self.checked_options:
if f.isChecked():
self.sel[f.parent().title()] = f.text()
if self.callback:
self.callback()
and pass in the callback:
def get_options(self):
sample_dict = {'GrpA' : ['John', 'Zack'], 'GrpB' : ['Alice', 'Phan']}
self.subWin = SubMenuWindow(sample_dict, callback=self.get_results)
[...]
this way your get_result method will be called whenever the user clicks the apply button in the SubMenuWindow.
I am new to python and pyqt/pyside ...
i make customwidget class consist of 2 label (title & desc) which is example instance to add to Listwidget later ...
here is the complete clean code (pyside maya)
import PySide.QtCore as qc
import PySide.QtGui as qg
class CustomQWidget (qg.QWidget):
def __init__ (self, parent = None):
super(CustomQWidget, self).__init__(parent)
self.textQVBoxLayout = qg.QVBoxLayout()
self.titleLabel = qg.QLabel()
self.description = qg.QLabel()
self.textQVBoxLayout.addWidget(self.titleLabel)
self.textQVBoxLayout.addWidget(self.description)
self.setLayout(self.textQVBoxLayout)
def setTitle (self, text):
self.titleLabel.setText(text)
def setDescription (self, text):
self.description.setText(text)
class example_ui(qg.QDialog):
def __init__(self):
qg.QDialog.__init__(self)
self.myQListWidget = qg.QListWidget(self)
self.myQListWidget.currentItemChanged.connect(self.getTitleValue)
self.myQListWidget.setGeometry(qc.QRect(0,0,200,300))
# make instance customwidget item (just one)------
instance_1 = CustomQWidget()
instance_1.setTitle('First title')
instance_1.setDescription('this is a sample desc')
myQListWidgetItem = qg.QListWidgetItem(self.myQListWidget)
myQListWidgetItem.setSizeHint(instance_1.sizeHint())
self.myQListWidget.addItem(myQListWidgetItem)
self.myQListWidget.setItemWidget(myQListWidgetItem, instance_1)
def getTitleValue(self,val):
# i make assume something like below but didnt work
# print (self.myQListWidget.currentItem.titleLabel.text()
return 0
dialog = example_ui()
dialog.show()
now at getTitleValue function how do i get Title and desc value when i change selection ?
You should remember that the list items and corresponding widgets are not the same. Luckily, QListWidget tracks them and gives you access to the displayed widget if you provide the list item:
class example_ui(qg.QDialog):
def getTitleValue(self,val):
# parameter val is actually the same as self.myQListWidget.currentItem
selected_widget = self.myQListWidget.itemWidget(val)
print selected_widget.titleLabel.text()
return 0
Side note: I had to add a main loop in order for the app to be executed at all:
import sys # to give Qt access to parameters
# ... class definitions etc. ...
app = qg.QApplication(sys.argv)
dialog = example_ui()
dialog.show()
exec_status = app.exec_() # main loop
I have a simple GUI which uses key binds - something like this.
import Tkinter, tkFileDialog
class Foo(object):
def __init__(self, master):
master.bind('3', self.do_bar)
master.bind('9', self.load_new_config)
self.load_config()
if not self.conf:
self.load_new_config()
else:
self.load_data()
def load_config(self):
try:
self.conf = #get stuff from known file
except FailedToGetStuff:
self.conf = None
def load_new_config(self):
path = askopenfilename(initialdir='~')
self.conf = #get stuff from file in path
self.load_data()
def load_data(self):
#get data from self.conf, process and display
def do_bar(self):
#do stuff with displayed data
if __name__ == "__main__"
root = Tk()
Foo(root)
root.mainloop()
Now, this works just fine when load_config() finds what it was looking for. I can use the binds and even after using '9' and loading new config, everything works.
Problem is, if load_config() fails, self.conf gets set to None and load_new_conf gets called from __init__, the binds are no longer operational.
I figured out that the problem is caused by tkFileDialog.askopenfilename() being called from within __init__. What I don't understand is why this happens and how to get around it.
This code works for me:
import Tkinter, tkFileDialog
class Foo(object):
def __init__(self, master):
master.bind('<KeyPress-3>', self.do_bar)
master.bind('<KeyPress-9>', self.load_new_config)
self.load_config()
if not self.conf:
master.after(1, self.load_new_config)
else:
self.load_data()
def load_config(self):
try:
self.conf = None#get stuff from known file
except FailedToGetStuff:
self.conf = None
def load_new_config(self, e = 0):
path = tkFileDialog.askopenfilename(initialdir='~')
self.conf = None#get stuff from file in path
self.load_data()
def load_data(self, e = 0):
pass
#get data from self.conf, process and display
def do_bar(self, e = 0):
print 1
#do stuff with displayed data
if __name__ == "__main__":
root = Tkinter.Tk()
Foo(root)
root.mainloop()
I am trying to update one cell from a mysql database in my table but i am unable to pass the global variable to the cell. Currently I am getting an integer from a mysql database, then i try to globally define, then i pass the varaible to mystruct (the structure of the table), and finally i apply the mystruct to the table, and gives me the error AttributeError: 'Window' object has no attribute 'struct1', i know why since the self.mystruct1 is first used in __init__. Is there an alternative. Please look at the code below to understand.
*Note position does not matter
import sys
from PyQt4.QtGui import QTableWidget
from PyQt4 import QtGui,QtCore
import MySQLdb as mdb
import time
class Window(QtGui.QDialog,object):
def get_data_status(self):
self.model.execute("""SELECT cpu FROM table
WHERE date = (SELECT MAX(date) FROM table)""")
rows_status = self.model.fetchone()
self.listc1 = ['%s' % rows_status]#['%s %s' % self.rows_status]
self.lista1 = 'Juliet','Julietleft','Pong','Hulk'
self.listb1 = 'None','None','None','None'
self.mystruct1 = {'A':self.lista1, 'B':self.listb1, 'C':self.listc1}
print self.mystruct1
return self.mystruct1
# this is only for the temp time test
def new_data_status(self):
self.update_ready_status.emit()
update_ready_status = QtCore.pyqtSignal()
def __init__(self,parent=None):
super(Window, self).__init__()
self.layout = QtGui.QVBoxLayout(self)
self.db = mdb.connect('server','user','user','db')
self.model = self.db.cursor()
self.table1 = MyTableStatus(Window.get_data_status(self),145,4)
self.table1.repaint()
self.table1.reset()
self.layout.addWidget(self.table1)
self.update_ready_status.connect(self.get_data_status)
self.timer_status = QtCore.QTimer()
self.timer_status.timeout.connect(self.new_data_status)
# check every half-second
self.timer_status.start(1000*2)
class MyTableStatus(QTableWidget):
def sizeHint(self):
width = 0
for i in range(self.columnCount()):
width += self.columnWidth(i)
width += self.verticalHeader().sizeHint().width()
width += self.verticalScrollBar().sizeHint().width()
width += self.frameWidth()*2
return QtCore.QSize(width,self.height())
def __init__(self, thestruct,*args):
QTableWidget.__init__(self, *args)
self.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Preferred)
self.data = thestruct
self.setHorizontalHeaderLabels(['Server', 'Avg. Disk Queue','CPU Load',"Status"])
self.setmydata()
QTableWidget.setSortingEnabled(self,True)
def setmydata(self):
for n, key in enumerate(self.data):
for m, item in enumerate(self.data[key]):
newitem = QtGui.QTableWidgetItem(item)
self.setItem(m, n, newitem)
def main():
app = QtGui.QApplication(sys.argv)
app.setStyle(QtGui.QStyleFactory.create("plastique"))
main_window = Window()
main_window.repaint()
main_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Manual trigger (code 2):
import sys
from PyQt4.QtGui import QTableWidget
from PyQt4 import QtGui,QtCore,Qt
import MySQLdb as mdb
import time
class Window(QtGui.QDialog):
def __init__(self,parent=None):
super(Window, self).__init__()
self.custom_choice = QtGui.QLineEdit()
self.layout = QtGui.QVBoxLayout(self)
self.db = mdb.connect('serv','user','pass','db')
self.model = self.db.cursor()
self.button = QtGui.QPushButton('Test', self)
self.button.clicked.connect(self.updateAllViews)
self.layout.addWidget(self.button)
self.initialData = self.get_data_status()
self.table1 = MyTableStatus(self.initialData, 145, 4)
self.layout.addWidget(self.table1)
# check every half-second
def handleHeaderMenu(self, pos):
self.menu = QtGui.QMenu()
self.custom_choice.setPlaceholderText("Server")
self.wac = QtGui.QWidgetAction(self.menu)
self.wac.setDefaultWidget(self.custom_choice)
self.menu.setStyleSheet("QMenu::item {background-color: #264F7D;color: white; font-weight:bold;}")
self.menu.addAction("Choose Server to Monitor:")
self.menu.addSeparator()
self.actionJuliet = self.menu.addAction('Juliet')
self.actionJulietleft = self.menu.addAction('JulietLeft')
self.actionPong = self.menu.addAction('Pong')
self.actionHulk = self.menu.addAction('Hulk')
self.actionCustom = self.menu.addAction(self.wac)
self.connect(self.custom_choice, QtCore.SIGNAL("returnPressed()"),self.updateAllViews)
action = self.menu.exec_(QtGui.QCursor.pos())
if action == self.actionPong:
print("pong")
def get_data_status(self):
self.tx = self.custom_choice.text()
self.model.execute("show TABLES;")
table_array = []
table_names = self.model.fetchall()
for lines in table_names:
lines = str(lines)
lines = lines.strip("()""''"",")
table_array.append(lines)
if any("%s" % self.tx in s for s in table_array):
table_name = self.tx
self.model.execute("""SELECT computer_name
FROM %s""" % (table_name))
new_user_name = self.model.fetchall()
self.model.execute("""SELECT idle_time
FROM %s""" % (table_name))
new_idle = self.model.fetchall()
self.model.execute("""SELECT files_opened
FROM %s""" % (table_name))
new_files = self.model.fetchall()
self.model.execute("""SELECT active_time
FROM %s""" % (table_name))
new_active = self.model.fetchall()
self.model.execute("""SELECT session_type
FROM %s""" % (table_name))
new_session = self.model.fetchall()
# self.model.execute("""SELECT number_of_machines
# FROM %s WHERE date = (SELECT MAX(date)
# FROM %s""" % (table_name,table_name))
#new_machines = self.model.fetchall()
self.model.execute("""SELECT cpu
FROM %s""" % (table_name))
new_cpu_load = self.model.fetchall()
self.model.execute("""SELECT avg_disk_queue
FROM %s""" % (table_name))
new_disk_queue_load = self.model.fetchall()
new_data_user = [item0[0] for item0 in new_user_name]
new_data_idle = [item1[0] for item1 in new_idle]
new_data_files = [item2[0] for item2 in new_files]
new_data_active = [item3[0] for item3 in new_active]
new_data_session = [item4[0] for item4 in new_session]
new_data_cpu_load = [item5[0] for item5 in new_cpu_load]
new_data_disk_queue_load = [item6[0] for item6 in new_disk_queue_load]
# self.lista.append(new_data_user)
# self.listb.append(new_data_idle)
# self.listc.append(new_data_files)
# self.listd.append(new_data_active)
# self.liste.append(new_data_session)
# self.listf.append(new_data_cpu_load)
# self.listg.append(new_data_disk_queue_load)
self.lista = new_data_user
self.listb = new_data_disk_queue_load
self.listc = new_data_cpu_load
self.listd = new_data_active
self.liste = new_data_files
self.listf = new_data_session
self.listg = new_data_idle
self.mystruct2 = {'A':self.lista, 'B':self.listb, 'C':self.listc,'E':self.liste,'D':self.listd,'F':self.listf,'G':self.listg}
else:
self.NotFound()
return self.mystruct2
def updateAllViews(self):
_ = self.get_data_status()
self.updateTable()
def updateTable(self):
self.table1.updateFromDict(self.mystruct1)
class MyTableStatus(QTableWidget):
def __init__(self, thestruct, *args):
QTableWidget.__init__(self, *args)
self.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Preferred)
self.setHorizontalHeaderLabels(['Server', 'Avg. Disk Queue','CPU Load',"Status"])
self.setSortingEnabled(False)
self.data = {}
self.setmydata()
def updateFromDict(self, aDict):
self.data.clear()
self.data.update(aDict)
self.setmydata()
def setmydata(self):
for n, key in enumerate(self.data):
for m, item in enumerate(self.data[key]):
newitem = QtGui.QTableWidgetItem(item)
self.setItem(m, n, newitem)
def main():
app = QtGui.QApplication(sys.argv)
app.setStyle(QtGui.QStyleFactory.create("plastique"))
main_window = Window()
main_window.repaint()
main_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The logic in this code is a bit messy, but I can see your problem with the data not updating.
There is an initial database pull, and then you pass self.mystruct1 to your custom Table to show the data for the first time. On subsequent triggers, you then overwrite that dictionary in the main window, thinking that somehow the Table will have the same reference. What is happening is the Window has the new dictionary, and the Table is sitting there with the original object.
Even if you were to just clear the dict, and update its values, as opposed to overwriting it each time, the Table would still not know the dictionary changed. You would need to connect a signal to the table itself to refresh its data, or, call something directly on the table. Simply naming the attribute model doesn't give it the same functionality as a QModel.
This is a little bit of a side-note, but python convention usually puts the __init__ at the top of the class so people reading it can immediately see what sets up your class before then seeing its methods.
To fix this, first clear out some cruft. You don't need a signal to a slot that emits another signal in this case. It isn't doing anything beyond making it more confusing. Just connect a signal directly to a slot on the Table that will perform an update. Also get rid of the repaint and reset calls in your main window on the table.
You can take two paths to providing the data to the Table. Either you can directly update the data model on the Table from your window and then tell it to refresh on that, or, you can pass the new data over the signal and let the Table handle it...
class Window(QtGui.QDialog):
def __init__(self,parent=None):
super(Window, self).__init__()
...
initialData = self.get_data_status()
self.table1 = MyTableStatus(initialData, 145, 4)
...
self.timer_status = QtCore.QTimer()
self.timer_status.timeout.connect(self.updateAllViews)
# check every half-second
self.timer_status.start(1000*2)
def get_data_status(self):
...
self.mystruct1 = {'A':self.lista1, 'B':self.listb1, 'C':self.listc1}
return self.mystruct1
def updateAllViews(self):
_ = self.get_data_status()
self.updateTable()
def updateTable(self):
self.table1.updateFromDict(self.mystruct1)
class MyTableStatus(QTableWidget):
def __init__(self, thestruct, *args):
QTableWidget.__init__(self, *args)
self.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Preferred)
self.setHorizontalHeaderLabels(['Server', 'Avg. Disk Queue','CPU Load',"Status"])
self.setSortingEnabled(True)
self.data = {}
self.setmydata(thestruct)
def updateFromDict(self, aDict):
self.data.clear()
self.data.update(aDict)
self.setmydata()
def setmydata(self):
for n, key in enumerate(self.data):
for m, item in enumerate(self.data[key]):
newitem = QtGui.QTableWidgetItem(item)
self.setItem(m, n, newitem)
You can give your table initial data, but you also need to set it up to be updated by future database pulls. Here we simply connect the timer to a method that updates the local data, and then refreshes the various views you are using, including the table.
The Table now has a method that can take a dict, and update its own internal data structure.
A slight variation on this approach would be to emit the new data structure in a signal, and just fire that when your local data structure changes in the Window...
class Window(QtGui.QDialog):
update_ready = QtCore.pyqtSignal(dict)
def __init__(self,parent=None):
...
# call a generate update and emit wrapper
self.timer_status.timeout.connect(self.refreshData)
# connect each view with a slot that expects a dict
self.update_ready.connect(self.table1.updateFromDict)
...
def refreshData(self):
new_data = self.get_data_status()
self.update_ready.emit(new_data)
In this example, you just let the signal deliver the new data structure to the view on a slot that expects a dict (from the previous example).
There are a few things going on here:
You don't need the global declaration in your init method (textedit and rows are not global)
You're seeing the error you're seeing from the line: MyTableStatus(self.mystruct1, 145, 4) because you haven't defined the self.mystruct1 variable yet - you define it in the get_data_status method, which isn't called until the signal is emitted. You'll generally want to define your class members before doing anything else (just good practice)
I'm not really sure why you have the time.sleep(2) call in there, the processing of the signal should happen pretty quickly in Qt
You have to watch out when doing things like self.layout = QtGui.QVBoxLayout(self). The layout symbol is actually an inherited QWidget method (self.layout()). Setting this can potentially break Qt internals.
Generally, unless you are trying to connect other widgets to the update_ready_status signal, you don't really need it
Anyway, I'm not 100% sure what you are trying to do - but I hope that helps!