I've got a combo box with a custom completer that worked fine in PyQt4, but isn't working in PySide.
I have verified that the new completer is replacing the QComboBox's built in completer because inline completion is no longer occurring. However when run with PySide, the completer doesn't popup with a filtered list of options.
I've also tried ensuring that all text is all str or all unicode to avoid differences between the PyQt API 1 with QStrings and PySide's use of Python unicode types. Changing the text types has had no effect on either PyQt or PySide's behavior (PyQt keeps working, PySide doesn't work).
Here is my code:
from PySide import QtCore
from PySide import QtGui
#from PyQt4 import QtCore
#from PyQt4 import QtGui
class AdvComboBox(QtGui.QComboBox):
def __init__(self, parent=None):
super(AdvComboBox, self).__init__(parent)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
self.setEditable(True)
# add a filter model to filter matching items
self.pFilterModel = QtGui.QSortFilterProxyModel(self)
self.pFilterModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.pFilterModel.setSourceModel(self.model())
# add a completer, which uses the filter model
self.completer = QtGui.QCompleter(self.pFilterModel, self)
# always show all (filtered) completions
self.completer.setCompletionMode(QtGui.QCompleter.UnfilteredPopupCompletion)
self.setCompleter(self.completer)
# connect signals
def filter(text):
print "Edited: ", text, "type: ", type(text)
self.pFilterModel.setFilterFixedString(str(text))
self.lineEdit().textEdited[unicode].connect(filter)
self.completer.activated.connect(self.on_completer_activated)
# on selection of an item from the completer, select the corresponding item from combobox
def on_completer_activated(self, text):
print "activated"
if text:
print "text: ", text
index = self.findText(str(text))
print "index: ", index
self.setCurrentIndex(index)
# on model change, update the models of the filter and completer as well
def setModel(self, model):
super(AdvComboBox, self).setModel(model)
self.pFilterModel.setSourceModel(model)
self.completer.setModel(self.pFilterModel)
# on model column change, update the model column of the filter and completer as well
def setModelColumn(self, column):
self.completer.setCompletionColumn(column)
self.pFilterModel.setFilterKeyColumn(column)
super(AdvComboBox, self).setModelColumn(column)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
combo = AdvComboBox()
names = ['bob', 'fred', 'bobby', 'frederick', 'charles', 'charlie', 'rob']
# fill the standard model of the combobox
combo.addItems(names)
combo.setModelColumn(0)
combo.resize(300, 40)
combo.show()
sys.exit(app.exec_())
I figured it out while writing the question...
It appears that while the PySide QCompleter documentation lists an option to initialize the QCompleter with a model and a parent, it isn't actually working.
The solution is to set the model of the completer after it is initialized.
Here is the working code:
from PySide import QtCore
from PySide import QtGui
class AdvComboBox(QtGui.QComboBox):
def __init__(self, parent=None):
super(AdvComboBox, self).__init__(parent)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
self.setEditable(True)
# add a filter model to filter matching items
self.pFilterModel = QtGui.QSortFilterProxyModel(self)
self.pFilterModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.pFilterModel.setSourceModel(self.model())
# add a completer
self.completer = QtGui.QCompleter(self)
#Set the model that the QCompleter uses
# - in PySide doing this as a separate step worked better
self.completer.setModel(self.pFilterModel)
# always show all (filtered) completions
self.completer.setCompletionMode(QtGui.QCompleter.UnfilteredPopupCompletion)
self.setCompleter(self.completer)
# connect signals
def filter(text):
print "Edited: ", text, "type: ", type(text)
self.pFilterModel.setFilterFixedString(str(text))
self.lineEdit().textEdited[unicode].connect(filter)
self.completer.activated.connect(self.on_completer_activated)
# on selection of an item from the completer, select the corresponding item from combobox
def on_completer_activated(self, text):
print "activated"
if text:
print "text: ", text
index = self.findText(str(text))
print "index: ", index
self.setCurrentIndex(index)
# on model change, update the models of the filter and completer as well
def setModel(self, model):
super(AdvComboBox, self).setModel(model)
self.pFilterModel.setSourceModel(model)
self.completer.setModel(self.pFilterModel)
# on model column change, update the model column of the filter and completer as well
def setModelColumn(self, column):
self.completer.setCompletionColumn(column)
self.pFilterModel.setFilterKeyColumn(column)
super(AdvComboBox, self).setModelColumn(column)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
combo = AdvComboBox()
names = ['bob', 'fred', 'bobby', 'frederick', 'charles', 'charlie', 'rob']
# fill the standard model of the combobox
combo.addItems(names)
combo.setModelColumn(0)
combo.resize(300, 40)
combo.show()
sys.exit(app.exec_())
Related
I am trying to have a series of QTableView created at runtime and added to newly created pages of a multipage QTabWidget.
All seems to go fine, but the QTableView don't show up.
The QTabWidget gets zeroed (reset to no pages) and refurbished (...) flawlessly (at least it looks like so) depending on the selection of a combobox (and the dictionaries therein related).
I am also using a delegate callback to include a column of checkboxes to the QTableView (thanks to https://stackoverflow.com/a/50314085/7710452), which works fine stand alone.
Here is the code.
Main Window
EDIT
as recommended by eyllanesc, here is the standalone module (jump to the end of the post for details on the part I think is problematic):
"""
qt5 template
"""
import os
import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
from PyQt5 import uic
from configparser import ConfigParser, ExtendedInterpolation
from lib.SearchControllers import findGuis, get_controller_dict, show_critical, show_exception
import resources.resources
from lib.CheckBoxesDelegate import CheckBoxDelegate
myForm_2, baseClass = uic.loadUiType('./forms/setup.ui')
class MainWindow(baseClass):
def __init__(self, config_obj: ConfigParser,
config_name: str,
proj_name: str,
*args,
**kwargs):
super().__init__(*args, **kwargs)
self.ui = myForm_2()
self.ui.setupUi(self)
# your code begins here
self.setWindowTitle(proj_name + " Setup")
self.ui.logo_lbl.setPixmap(qtg.QPixmap(':/logo_Small.png'))
self.config_obj = config_obj
self.config_name = config_name
self.proj_filename = proj_name
self.proj_config = ConfigParser(interpolation=ExtendedInterpolation())
self.proj_config.read(proj_name)
self.guis_dict = {}
self.components = {}
self.cdp_signals = {}
self.root_path = self.config_obj['active']['controllers']
self.tableViews = []
self.tabs = []
self.iniControllersBox()
self.setActSignals()
self.load_bulk()
self.set_signals_table()
self.update_CurController_lbl()
self.update_ControllersTab() # here is where the action gets hot
# your code ends here
self.show() # here crashes if I passed the new tab to the instance of
# QTabView. otherwise it shows empty tabs
#########################################################
def load_bulk(self):
# get the list of running components into a dictionary
for i in self.list_controllers:
i_path = os.path.join(self.root_path, i)
print(i)
self.components[i] = get_controller_dict(i_path,
self.config_obj,
'Application.xml',
'Subcomponents/Subcomponent',
'Name',
'src')
for j in self.components[i]:
print(j)
signals_key = (i , j)
tgt = os.path.join(self.root_path, self.components[i][j])
self.cdp_signals[signals_key] = get_controller_dict(i_path,
self.config_obj,
self.components[i][j],
'Signals/Signal',
'Name',
'Type',
'Routing')
def set_signals_table(self):
self.ui.MonitoredDevicesTable.setHorizontalHeaderItem(0, qtw.QTableWidgetItem('GUI caption'))
self.ui.MonitoredDevicesTable.setHorizontalHeaderItem(1, qtw.QTableWidgetItem('Monitored Signal'))
def setActSignals(self):
self.ui.controllersBox.currentIndexChanged.connect(self.update_guis_list)
self.ui.controllersBox.currentIndexChanged.connect(self.update_CurController_lbl)
self.ui.controllersBox.currentIndexChanged.connect(self.update_ControllersTab)
def update_ControllersTab(self):
self.ui.componentsTab.clear() # this is the QTabWidget
self.tabs = []
self.tableViews = []
curr_controller = self.ui.controllersBox.currentText()
for i in self.components[curr_controller]:
if len(self.cdp_signals[curr_controller, i]) == 0:
continue
self.tabs.append(qtw.QWidget())
tabs_index = len(self.tabs) - 1
header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
model = qtg.QStandardItemModel(len(self.cdp_signals[curr_controller, i]), 5)
model.setHorizontalHeaderLabels(header_labels)
# in the next line I try to create a new QTableView passing
# the last tab as parameter, in the attempt to embed the QTableView
# into the QWidget Tab
self.tableViews.append(qtw.QTableView(self.tabs[tabs_index]))
tbw_Index = len(self.tableViews) - 1
self.tableViews[tbw_Index].setModel(model)
delegate = CheckBoxDelegate(None)
self.tableViews[tbw_Index].setItemDelegateForColumn(0, delegate)
rowCount = 0
for row in self.cdp_signals[curr_controller, i]:
for col in range(len(self.cdp_signals[curr_controller, i][row])):
index = model.index(rowCount, col, qtc.QModelIndex())
model.setData(index, self.cdp_signals[curr_controller, i][row][col])
try:
self.ui.componentsTab.addTab(self.tabs[tabs_index], i) # no problems, some controllers ask up to
except Exception as ex:
print(ex)
def update_CurController_lbl(self):
self.ui.active_controller_lbl.setText(self.ui.controllersBox.currentText())
def iniControllersBox(self):
self.list_controllers = [os.path.basename(f.path) for f in os.scandir(self.root_path) if f.is_dir() and str(
f.path).upper().endswith('NC')]
self.ui.controllersBox.addItems(self.list_controllers)
for i in range(self.ui.controllersBox.count()):
self.ui.controllersBox.setCurrentIndex(i)
newKey = self.ui.controllersBox.currentText()
cur_cntrlr = os.path.join(self.config_obj['active']['controllers'], self.ui.controllersBox.currentText())
self.guis_dict[newKey] = findGuis(cur_cntrlr, self.config_obj)
self.ui.controllersBox.setCurrentIndex(0)
self.update_guis_list()
def update_guis_list(self, index=0):
self.ui.GuisListBox.clear()
self.ui.GuisListBox.addItems(self.guis_dict[self.ui.controllersBox.currentText()])
if __name__ == '__main__':
config = ConfigParser()
config.read('./config.ini')
app = qtw.QApplication([sys.argv])
w = MainWindow(config, './config.ini',
'./test_setup_1.proj')
sys.exit(app.exec_())
and here the external to add the checkboxes column:
class CheckBoxDelegate(QtWidgets.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
"""
def __init__(self, parent):
QtWidgets.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
"""
Important, otherwise an editor is created if the user clicks in this cell.
"""
return None
def paint(self, painter, option, index):
"""
Paint a checkbox without the label.
"""
self.drawCheck(painter, option, option.rect, QtCore.Qt.Unchecked if int(index.data()) == 0 else QtCore.Qt.Checked)
def editorEvent(self, event, model, option, index):
'''
Change the data in the model and the state of the checkbox
if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
'''
if not int(index.flags() & QtCore.Qt.ItemIsEditable) > 0:
return False
if event.type() == QtCore.QEvent.MouseButtonRelease and event.button() == QtCore.Qt.LeftButton:
# Change the checkbox-state
self.setModelData(None, model, index)
return True
if event.type() == QtCore.QEvent.MouseButtonPress or event.type() == QtCore.QEvent.MouseMove:
return False
return False
def setModelData (self, editor, model, index):
'''
The user wanted to change the old state in the opposite.
'''
model.setData(index, 1 if int(index.data()) == 0 else 0, QtCore.Qt.EditRole)
The 1st picture shows the layout in QTDesigner, the 2nd the result (emtpy tabs) when avoiding the crashing.
the QTabWidget has no problems in zeroing, or scale up, back to as many tab as I need, it's just that I have no clue on how to show the QTabview. My approach was to try to embed the QTabView in the tabpage passing it as parameter to the line creating the new QTabView.
Since I am using rather convoluted dictionaries, calling an XML parser to fill them up, not to mention the config files, I know even this version of my script is hardly reproduceable/runnable.
If someone had the patience of focusing on the update_ControllersTab method though, and tell me what I am doing wrong handling the QWidgets, it'd be great.
Again the basic idea is to clear the QTabWidget any time the user selects a different controller (combo box on the left):
self.ui.componentsTab.clear() # this is the QTabWidget
self.tabs = [] # list to hold QTabView QWidgets (pages) throughout the scope
self.tableViews = [] # list to hold QTabView(s) thorughout the scope
count how many tabs (pages) and hence embedded TabViews I need with the new controllers selected.
and then for each tab needed:
create a new tab (page)
self.tabs.append(qtw.QWidget())
tabs_index = len(self.tabs) - 1
create a new QTabView using a model:
header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
model = qtg.QStandardItemModel(len(self.cdp_signals[curr_controller, i]), 5)
model.setHorizontalHeaderLabels(header_labels)
self.tableViews.append(qtw.QTableView(self.tabs[tabs_index]))
tbw_Index = len(self.tableViews) - 1
self.tableViews[tbw_Index].setModel(model)
populate the TableView with data, and then finally add the tab widget (with the suppposedly embedded QTableView to the QTabWidget (the i argument is a string from my dbases Names:
self.ui.componentsTab.addTab(self.tabs[tabs_index], i)
This method is called also by the __init__ to initialize and apparently all goes error free, until the last 'init' statement:
`self.show()`
at which point the app crashes with:
Process finished with exit code 1073741845
on the other hand, if here instead of trying to embed the QTableView:
self.tableViews.append(qtw.QTableView(self.tabs[tabs_index]))
I omit the parameter, that is:
self.tableViews.append(qtw.QTableView())
the app doesn't crash anymore, but of course no QtableViews are shown, only empty tabpages:
As stupid as this may sound the problem is in... the delegate class that creates the checkboxes in the first column (see https://stackoverflow.com/a/50314085/7710452)
I commented out those two lines:
delegate = CheckBoxDelegate(None)
self.tableViews[tbw_Index].setItemDelegateForColumn(0, delegate)
and... bingo!
the CheckBoxDelegate works fine in the example shown in the post (a single QTableView form). I also tinkered around adding columns and rows, and moving the checkbox column back and forth with no problems. In that standalone. But as soon as I add the class and set the delegate, i am back at square zero, app crashing with:
Process finished with exit code 1073741845
so I am left with this problem now. Thnx to whomever read this.
Problem solved, see comment to post above.
PyQt5 - QComboBox populated with QQueryModel including Id; Name; ..etc fields. Using Name as QComboBox.modelColumn. Hope to retrive id field from the clicked item.
I shifted the ModelColumn back and forth without doing any good. Also I accessed the QQueryModel.record to find that it is on the first record always, not on the current one.
import MySQL_Connector
import sys
from PyQt5.QtCore import QVariant, Qt
from PyQt5.QtSql import QSqlDatabase, QSqlQueryModel,QSqlQuery , QSqlTableModel, QSqlError, QSqlQueryModel, QSqlQuery
from PyQt5.QtWidgets import QApplication, QMainWindow
from MainUI import Ui_MainWindow
class QConnectionError(Exception):
pass
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
#setup Qsql databaase objects
cnn = MySQL_Connector.MysqlConnection('config.ini')
con_string = cnn.read_db_config()[1]
try:
db = QSqlDatabase.addDatabase("QMYSQL")
db.setHostName(con_string['host'])
db.setUserName(con_string['user'])
db.setDatabaseName(con_string['database'])
db.setPassword(con_string['password'])
ok = db.open()
if not ok:
raise QConnectionError("Connection failed--- Is the server running?")
except QConnectionError as err:
print("You'll have to wait until a connection is established")
return
finally:
if db.isOpen():
db.close()
self.qdb = db
self.qdb.open()
# set combobox
self.comboQuery = combo_query(self.qdb)
self.comboModel = QSqlQueryModel()
self.comboModel.setQuery(self.comboQuery)
self.comboBox.setModel(self.comboModel)
self.comboBox.setModelColumn(1)
self.comboBox.activated[int].connect(self.do_action)
#populate textView
self.query = test(self.qdb)
self.model = QSqlQueryModel()
self.model.setQuery(self.query)
self.model.setHeaderData(0,Qt.Horizontal, "ID")
self.model.setHeaderData(1, Qt.Horizontal, "Nombre")
self.tableView.rowHeight(2)
self.tableView.fontMetrics()
self.tableView.setModel(self.model)
self.show()
if self.qdb.isOpen():
self.qdb.close()
def do_action(self, str): #Experimenting
print(str, type(str))
self.tableView.selectRow(5)
def main():
app = QApplication(sys.argv)
window = MainWindow()
app.exec()
Need advise on how to go forward. All the database I'm using is based on ID fields with I need to further query for whatever the reason. Maybe a tools change? A different approach.
QCombobox has two signals suitable for your needs:
activated() is sent when the user chooses an item, even if the choice is not changed, see QComboBox.activated()
currentIndexChanged() is sent if the current index is changed, either by users choice or programmatically, see QComboBox.currentIndexChanged()
Both signals pass the current index.
Using this index you get the needed data by QSqlQuery.data().
Here a simple example using sqlite3, i think you can adapt it to your database:
import sqlite3
from PyQt5 import QtWidgets, QtSql
class MyWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.database = QtSql.QSqlDatabase('QSQLITE')
self.database.setDatabaseName('tc.db')
self.database.open()
self.dataModel = QtSql.QSqlQueryModel()
self.dataModel.setQuery('select id, name from items', self.database)
self.comboBox = QtWidgets.QComboBox(self)
self.comboBox.setModel(self.dataModel)
self.comboBox.setModelColumn(1)
self.comboBox.currentIndexChanged.connect(self.do_action)
# self.comboBox.activated.connect(self.do_action)
def do_action(self, i):
id = self.dataModel.data(self.dataModel.index(i, 0)) # self.dataModel.index(row, column)
name = self.dataModel.data(self.dataModel.index(i, 1))
print(i, 'id: ', id, 'name: ', name)
qApp = QtWidgets.QApplication([])
widget = MyWidget()
widget.show()
qApp.exec_()
here the dump of the database tc.db:
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "items" (
"id" INTEGER PRIMARY KEY NOT NULL,
"name" TEXT NOT NULL
);
INSERT INTO items VALUES(0,'abcde');
INSERT INTO items VALUES(1,'fghijk');
INSERT INTO items VALUES(2,'lmnop');
INSERT INTO items VALUES(3,'qrstuv');
COMMIT;
I'm sorry but just a beginner of Python.
I just want to change index of QStackedWidget by the item click of QTreeWidget. I searched for the tutorials of SIGNAL and SLOT online, but just cannot solve the problem.
The parameters in QTreeWidget signal and QStackedWidget slot are not fitted.
self.connect(qtree, QtCore.SIGNAL("itemClicked(QTreeWidgetItem*,int)"), stack, QtCore.SLOT("setCurrentIndex(int)"))
And I tried this:
qtree.itemClicked.connect(stack.setCurrentIndex)
It just showed the error:
TypeError: setCurrentIndex(self, int): argument 1 has unexpected type 'QTreeWidgetItem'
I think there may be a method, but I cannot find on the network.
Like this:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class StockDialog(QDialog):
def __init__(self,parent=None):
super(StockDialog,self).__init__(parent)
mainSplitter=QSplitter(Qt.Horizontal)
treewidget = QTreeWidget(mainSplitter)
treewidget.setHeaderLabels(["Tree"])
treeroot = QTreeWidgetItem(treewidget, ["Stack"])
treeitem1 = QTreeWidgetItem(["WorkSpace"])
treeitem2 = QTreeWidgetItem(["About"])
treeroot.addChild(treeitem1)
treeroot.addChild(treeitem2)
stack=QStackedWidget(mainSplitter)
stack.setFrameStyle(QFrame.Panel|QFrame.Raised)
stackworkspace=StackWorkSpace()
stackabout=StackAbout()
stack.addWidget(stackworkspace)
stack.addWidget(stackabout)
closePushButton=QPushButton(self.tr("Close"))
self.connect(treewidget,
SIGNAL("itemClicked(int)"),
stack,SLOT("setCurrentIndex(int)"))
self.connect(closePushButton,
SIGNAL("clicked()"),
self,SLOT("close()"))
layout=QVBoxLayout(self)
layout.addWidget(mainSplitter)
layout.addWidget(closePushButton)
self.setLayout(layout)
class StackWorkSpace(QWidget):
def __init__(self,parent=None):
super(StackWorkSpace,self).__init__(parent)
widget1=QTextEdit(self.tr("WorkSpace"))
widget2=QTextEdit(self.tr("WorkSpace"))
layout=QGridLayout(self)
layout.addWidget(widget1,0,0)
layout.addWidget(widget2,0,1)
class StackAbout(QDialog):
def __init__(self,parent=None):
super(StackAbout,self).__init__(parent)
self.setStyleSheet("background: red")
app=QApplication(sys.argv)
main=StockDialog()
main.show()
app.exec_()
When change the QTreeWidget to the QListWidget in StockDialog class, it works.
class StockDialog(QDialog):
def __init__(self,parent=None):
super(StockDialog,self).__init__(parent)
mainSplitter=QSplitter(Qt.Horizontal)
listwidget=QListWidget(mainSplitter)
listwidget.insertItem(0,self.tr("WorkSpace"))
listwidget.insertItem(1,self.tr("About"))
stack=QStackedWidget(mainSplitter)
stack.setFrameStyle(QFrame.Panel|QFrame.Raised)
stackworkspace=StackWorkSpace()
stackabout=StackAbout()
stack.addWidget(stackworkspace)
stack.addWidget(stackabout)
closePushButton=QPushButton(self.tr("Close"))
self.connect(listwidget,
SIGNAL("currentRowChanged(int)"),
stack,SLOT("setCurrentIndex(int)"))
self.connect(closePushButton,
SIGNAL("clicked()"),
self,SLOT("close()"))
layout=QVBoxLayout(self)
layout.addWidget(mainSplitter)
layout.addWidget(closePushButton)
self.setLayout(layout)
Now, I want to do this with QTreeWidget, how can I do?
The strategy to solve this problem is to save the index information associated with each widget in the QTreeWidgetItem. QTreeWidgetItem has the setData() method that allows us to save information in the item and in this case we will save the index. The index is returned every time you add a widget to QStackedWidget through addWidget(), so in summary we will do the following:
treeitem1.setData(0, Qt.UserRole, stack.addWidget(stackworkspace))
treeitem2.setData(0, Qt.UserRole, stack.addWidget(stackabout))
After connecting the itemClicked signal of QTreeWidget, this returns the column and the item pressed, with this information we obtain the QStackedWidget index for it we recover the data saved through the function data():
treewidget.itemClicked.connect(lambda item, column: stack.setCurrentIndex(item.data(column, Qt.UserRole))
if item.data(column, Qt.UserRole) is not None else None)
The necessary code can be found in the following section:
class StockDialog(QDialog):
def __init__(self, parent=None):
super(StockDialog, self).__init__(parent)
mainSplitter = QSplitter(Qt.Horizontal)
treewidget = QTreeWidget(mainSplitter)
treewidget.setHeaderLabels(["Tree"])
treeroot = QTreeWidgetItem(treewidget, ["Stack"])
treeitem1 = QTreeWidgetItem(["WorkSpace"])
treeitem2 = QTreeWidgetItem(["About"])
treeroot.addChild(treeitem1)
treeroot.addChild(treeitem2)
stack = QStackedWidget(mainSplitter)
stack.setFrameStyle(QFrame.Panel | QFrame.Raised)
stackworkspace = StackWorkSpace()
stackabout = StackAbout()
treeitem1.setData(0, Qt.UserRole, stack.addWidget(stackworkspace))
treeitem2.setData(0, Qt.UserRole, stack.addWidget(stackabout))
closePushButton = QPushButton(self.tr("Close"))
treewidget.itemClicked.connect(lambda item, column: stack.setCurrentIndex(item.data(column, Qt.UserRole))
if item.data(column, Qt.UserRole) is not None else None)
layout = QVBoxLayout(self)
layout.addWidget(mainSplitter)
layout.addWidget(closePushButton)
self.setLayout(layout)
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 reduced code sample in which a window is created with just one Button.
Pressing it will pop a Qt dialog TestDialog which takes as parameter a Python dictionary. This dictionary is displayed in an editable QTreeView inside the dialog.
After changing some values you can click on Ok or Cancel to either accept or discard the changes. Once the dialog is closed my intention is to retrieve from the main window the modified dictionary calling dialog.get_data() which, right now only returns the original unmodified dictionary. After clicking the Ok button, the retrieved dictionary is printed to stdout.
My question is, how can I modify the dictionary when the tree view is modified? Is there a way to automatically attach a function to be executed on each item modification? So that when editing, for example, a float in the tree view, then the corresponding value will be updated as float in the dictionary?
The dictionary does not have a fixed size and the types on it may change. The list of types is limited and known though and, for this example, could be reduced to {int, str, float, Other}. It can be assumed as well that the parents are not supposed to be editable and the children are only editable in the second column, just as it is shown in the example bellow.
Here is the code I have:
import sys
from PyQt4 import QtGui, QtCore, uic
from copy import deepcopy
class TestDialog(QtGui.QDialog):
def __init__(self, data):
super(TestDialog, self).__init__()
self.data = deepcopy(data)
# Layout
btOk = QtGui.QPushButton("OK")
btCancel = QtGui.QPushButton("Cancel")
self.tree = QtGui.QTreeView()
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(btOk)
hbox.addWidget(btCancel)
vbox = QtGui.QVBoxLayout()
vbox.addLayout(hbox)
vbox.addWidget(self.tree)
self.setLayout(vbox)
self.setGeometry(300, 300, 600, 400)
# Button signals
btCancel.clicked.connect(self.reject)
btOk.clicked.connect(self.accept)
# Tree view
self.tree.setModel(QtGui.QStandardItemModel())
self.tree.setAlternatingRowColors(True)
self.tree.setSortingEnabled(True)
self.tree.setHeaderHidden(False)
self.tree.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
self.tree.model().setHorizontalHeaderLabels(['Parameter', 'Value'])
for x in self.data:
if not self.data[x]:
continue
parent = QtGui.QStandardItem(x)
parent.setFlags(QtCore.Qt.NoItemFlags)
for y in self.data[x]:
value = self.data[x][y]
child0 = QtGui.QStandardItem(y)
child0.setFlags(QtCore.Qt.NoItemFlags |
QtCore.Qt.ItemIsEnabled)
child1 = QtGui.QStandardItem(str(value))
child1.setFlags(QtCore.Qt.ItemIsEnabled |
QtCore.Qt.ItemIsEditable |
~ QtCore.Qt.ItemIsSelectable)
parent.appendRow([child0, child1])
self.tree.model().appendRow(parent)
self.tree.expandAll()
def get_data(self):
return self.data
class Other(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return '(%s, %s)' % (self.x, self.y)
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
btn = QtGui.QPushButton('Button', self)
btn.resize(btn.sizeHint())
btn.clicked.connect(self.show_dialog)
self.data = {}
# This example will be hidden (has no parameter-value pair)
self.data['example0'] = {}
# A set example with an integer and a string parameters
self.data['example1'] = {}
self.data['example1']['int'] = 14
self.data['example1']['str'] = 'asdf'
# A set example with a float and other non-conventional type
self.data['example2'] = {}
self.data['example2']['float'] = 1.2
self.data['example2']['other'] = Other(4, 8)
def show_dialog(self):
dialog = TestDialog(self.data)
accepted = dialog.exec_()
if not accepted:
return
self.data = deepcopy(dialog.get_data())
print self.data
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
You can connect to the model's itemChanged signal:
self.tree.model().itemChanged.connect(self.handleItemChanged)
The handler would look something like this:
def handleItemChanged(self, item):
parent = self.data[item.parent().text()]
key = item.parent().child(item.row(), 0).text()
parent[key] = type(parent[key])(item.text())
Note that the conversion of values using type won't necessarily work for custom classes like Other. So you will have to either ensure that the constructor for such classes can convert a string representation, or parse the string into the appropriate arguments before passing them to the constructor.
Also note that I haven't bothered to deal with QString values in the above example code. If you use Python 3, this is not an issue, because they are automatically converted to/from Python strings by default. But for Python 2, you can switch this behaviour on by doing the following:
import sip
sip.setapi('QString', 2)
from PyQt4 import QtGui, QtCore, uic
For more details on this, see Selecting Incompatible APIs in the PyQt4 Docs.