Getting changed data from PyQt5 QDialog - python-3.x

I've got a configuration dialog that I'm pre-populating with the existing options, which are stored in cfg. When the user clicks "Save" (or equivalent), I need to get the new values from the QLineEdit objects. Except I can't figure that bit out. I've been Googling and testing since yesterday evening, before I came here on bended knee yet again. Here's my dialog code (the form is from Qt Designer, which is why there's no GUI code):
class Config(QDialog):
def __init__(self):
super(Config, self).__init__()
popup = QDialog()
config_ui = configform()
config_ui.setupUi(popup)
config_ui.programver.setText(cfg['config']['programver'])
if cfg['config']['dummycopy']:
config_ui.democheck.setChecked(True)
config_ui.tmdbAPIkey.setText(cfg['config']['TMDB_KEY'])
config_ui.tvdbAPIkey.setText(cfg['config']['TVDB_KEY'])
config_ui.tvdbUserkey.setText(cfg['config']['TVDB_USERKEY'])
theme = cfg['config']['theme']
if theme == "blue":
config_ui.bluebutton.setChecked(True)
elif theme == "yellow":
config_ui.yellowbutton.setChecked(True)
elif theme == "light":
config_ui.lightmetalbutton.setChecked(True)
elif theme == "dark":
config_ui.darkmetalbutton.setChecked(True)
programversion = config_ui.programver.text()
config_ui.savebutton.clicked.connect(lambda: Config.save(self, programversion))
popup.exec_()
def save(self, programversion):
QDialog.close(self)
print(programversion)
I need some voodoo to get at the changed fields. All I can get now are the original values from when the dialog was brought to life. Is there a trick to this? I can't be the first person to try pre-populating a dialog box. I swear I've tried every combination of button and buttonBox variations available.
I'm thinking maybe there's some way of hiding the dialog, grabbing the data, and then destroying the dialog? That's one working theory, anyway.
Thanks in advance.

To work in a simple way we use the design of Qt Designer to fill the Dialog, and we connect the cancel button to self.reject () and the save button to the save () slot, in this we save the data and issue self.accept () :
from PyQt5.QtWidgets import *
from Ui_config_dialog import Ui_configdialog
import configparser
class Config(QDialog, Ui_configdialog):
def __init__(self, *args, **kwargs):
QDialog.__init__(self, *args, **kwargs)
self.setupUi(self)
self.cancelbutton.clicked.connect(self.reject)
self.filename = "joe.conf"
self.cfg = configparser.ConfigParser()
self.cfg.read(self.filename)
self.load()
def load(self):
self.programver.setText(self.cfg['config']['programver'])
self.democheck.setChecked(self.cfg.getboolean("config", "dummycopy"))
self.tmdbAPIkey.setText(self.cfg['config']['TMDB_KEY'])
self.tvdbAPIkey.setText(self.cfg['config']['TVDB_KEY'])
self.tvdbUserkey.setText(self.cfg['config']['TVDB_USERKEY'])
theme = self.cfg['config']['theme']
self.buttons = {"blue": self.bluebutton,
"yellow": self.yellowbutton,
"light": self.lightmetalbutton,
"dark": self.darkmetalbutton}
self.buttons[theme].setChecked(True)
self.group = QButtonGroup(self)
self.group.addButton(self.bluebutton)
self.group.addButton(self.yellowbutton)
self.group.addButton(self.lightmetalbutton)
self.group.addButton(self.darkmetalbutton)
self.savebutton.clicked.connect(self.save)
def save(self):
self.cfg['config']['programver'] = self.programver.text()
self.cfg['config']['dummycopy'] = "True" if self.democheck.isChecked() else "False"
self.cfg['config']['TMDB_KEY'] = self.tmdbAPIkey.text()
self.cfg['config']['TVDB_KEY'] = self.tvdbUserkey.text()
for key, btn in self.buttons.items():
if btn == self.group.checkedButton():
self.cfg['config']['theme'] = key
break
with open(self.filename, 'w') as configfile:
self.cfg.write(configfile)
self.accept()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = Config()
w.show()
sys.exit(app.exec_())

Related

Text of label in UI file can't be changed

I tried it.
from PySide2 import QtWidgets
from PySide2 import QtGui
from PySide2 import QtCore
from PySide2.QtUiTools import QUiLoader
from maya.app.general.mayaMixin import MayaQWidgetBaseMixin
import shiboken2 as shiboken
import os
UIFILEPATH = 'D:/MAYA/pyside_pick/ui/PicsTest5.ui'
class MainWindow(MayaQWidgetBaseMixin,QtWidgets.QMainWindow):
def __init__(self,parent=None):
super(MainWindow,self).__init__(parent)
self.UI = QUiLoader().load(UIFILEPATH)
self.setWindowTitle(self.UI.windowTitle())
self.setCentralWidget(self.UI)
#image
img = QtGui.QPixmap('D:/MAYA/pyside_pick/images/imgKohaku.png')
self.scene = QtWidgets.QGraphicsScene(self)
item = QtWidgets.QGraphicsPixmapItem(img)
self.scene.addItem(item)
self.UI.graphicsView_char_1.setScene(self.scene)
#filter
self._filter = Filter()
self.installEventFilter(self._filter)
self.UI.pSphere1.installEventFilter(self._filter)
#primary
self.UI.label.setStyleSheet("QLabel {color : white;}")
self.UI.label.setText("A")
def labelTest(self):
self.UI.label.setStyleSheet("QLabel {color : red;}")
self.UI.label.setText("B")
print('D')
return False
class Filter(QtCore.QObject):
def eventFilter(self, widget, event):
win = MainWindow()
if event.type() == QtCore.QEvent.MouseButtonPress:
print(widget.objectName())
cmds.select(widget.objectName())
win.labelTest()
return False
def main():
win = MainWindow()
win.show()
if __name__ == '__main__':
main()
I clicked the button that 'pSphere1', but
self.UI.label.setStyleSheet("QLabel {color : red;}") self.UI.label.setText("B")
were look like it's not working.
I can change it inside define with UI loaded, but can't I do setText from outside?
How can I change the label of an imported UI file?
I find this, but I really do not understand. I couldn't find any mention of them beyond this page.
Change comboBox values in Qt .ui file with PySide2
If you know, I also want you to tell me where to put them.
Your issue is within the eventFilter(), and specifically the first line:
win = MainWindow()
This will create a new main window instance, which clearly doesn't make sense, since you obviously want to interact with the existing one.
While you could add the instance as an argument in the filter constructor in order to get a reference to the instance and directly call the function, that wouldn't be very good from the OOP point of view, as objects should never directly access attributes of their "parents".
A better and more correct approach would be to use a custom signal instead, and connect it from the main window instance:
class Filter(QtCore.QObject):
testSignal = QtCore.Signal()
def eventFilter(self, widget, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
print(widget.objectName())
cmds.select(widget.objectName())
self.testSignal.emit()
return False
class MainWindow(MayaQWidgetBaseMixin, QtWidgets.QMainWindow):
def __init__(self, parent=None):
# ...
self._filter.testSignal.connect(self.labelTest)
Note that widgets could accept events and prevent propagation (for instance, buttons or graphics views that have selectable or movable items), so you might not receive the event in the filter in those cases.

How to implement a Virtual Tab Key in PyQt5 – nextInFocusChain() returns QFocusFrame

I'm trying to implement a button in PyQt5 which acts identically to pressing the Tab-Key. For this, I get the focused item, and call nextInFocusChain() to get the next item in tab order and set it to focus. If it is not a QLineEdit object, I repeat.
self.focusWidget().nextInFocusChain().setFocus()
while type(self.focusWidget()) != QLineEdit:
print(str(self.focusWidget()) + "" + str(self.focusWidget().nextInFocusChain()))
self.focusWidget().nextInFocusChain().setFocus()
Sadly this snipped does not wrap around. After the last QLineEdit, it gets stuck inside the loop while continuously printing
...
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
Here a reproducing example...
from collections import OrderedDict
from PyQt5.QtWidgets import QDialog, QFormLayout, QLineEdit, QCheckBox, QDialogButtonBox, QVBoxLayout, QPushButton
class AddressEditor(QDialog):
def __init__(self,fields:OrderedDict,parent=None):
super(AddressEditor, self).__init__(parent=parent)
layout = QVBoxLayout(self)
form = QFormLayout(self)
self.inputs = OrderedDict()
last = None
for k,v in fields.items():
self.inputs[k] = QLineEdit(v)
if last is not None:
self.setTabOrder(self.inputs[last],self.inputs[k])
last = k
form.addRow(k, self.inputs[k])
layout.addLayout(form)
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
def tabpressed(self):
self.focusWidget().nextInFocusChain().setFocus()
while type(self.focusWidget()) != QLineEdit:
print(str(self.focusWidget()) + "" + str(self.focusWidget().nextInFocusChain()))
self.focusWidget().nextInFocusChain().setFocus()
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
dialog = AddressEditor({"Text":"Default","Empty":"","True":"True","False":"False"})
if dialog.exec():
pass
exit(0)
There are two important aspects to consider when trying to achieve "manual" focus navigation:
the focus chain considers all widgets, not only those that can "visibly" accept focus; this includes widgets that have a NoFocus policy, the parent widget (including the top level window), hidden widgets, and any other widget that can be "injected" by the style, including "helpers" like QFocusFrame;
widgets can have a focus proxy which can "push back" the focus to the previous widget, and this can cause recursion issues like in your case;
Besides that, there are other issues with your implementation:
the button accepts focus, so whenever it's pressed it resets the focus chain;
to compare the class you should use isinstance and not type;
form should not have any argument since it's going to be added as a nested layout, and in any case it shouldn't be self since a layout has already been set;
the tab order must be set after both widgets are added to parents that share the same a common ancestor widget/window;
the tab order is generally automatically set based on when/where widgets are added to the parent: for a form layout is generally unnecessary as long as all fields are inserted in order;
class AddressEditor(QDialog):
def __init__(self, fields:OrderedDict, parent=None):
super(AddressEditor, self).__init__(parent=parent)
layout = QVBoxLayout(self)
form = QFormLayout() # <- no argument
layout.addLayout(form)
self.inputs = OrderedDict()
last = None
for k, v in fields.items():
new = self.inputs[k] = QLineEdit(v)
form.addRow(k, new) # <- add the widget *before* setTabOrder
if last is not None:
self.setTabOrder(last, new)
last = new
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
button.setFocusPolicy(Qt.NoFocus) # <- disable focus for the button
def tabpressed(self):
nextWidget = self.focusWidget().nextInFocusChain()
while not isinstance(nextWidget, QLineEdit) or not nextWidget.isVisible():
nextWidget = nextWidget.nextInFocusChain()
nextWidget.setFocus()
If you want to keep the focus policy for the button so that it can be reached through Tab, the only possibility is to keep track of the focus change of the application, since as soon as the button is pressed with the mouse button it will already have received focus:
class AddressEditor(QDialog):
def __init__(self, fields:OrderedDict, parent=None):
# ...
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
QApplication.instance().focusChanged.connect(self.checkField)
self.lastField = tuple(self.inputs.values())[0]
def checkField(self, old, new):
if isinstance(new, QLineEdit) and self.isAncestorOf(new):
self.lastField = new
def tabpressed(self):
nextWidget = self.lastField.nextInFocusChain()
while not isinstance(nextWidget, QLineEdit) or not nextWidget.isVisible():
nextWidget = nextWidget.nextInFocusChain()
nextWidget.setFocus()

PySide2: How to re-implement QFormLayout.takeRow()?

I've noticed that QFormLayout in Pyside2 does not have the takeRow method like its PyQt5 counterpart. I've attempted to subclass QFormLayout to incorporate a similar method, but I've run into Runtime Errors, as the removal behavor of the LabelRole item is different than the FieldRole item. Another issue being that the LabelRole item does not actually get taken off the row even when the row itself is removed.
The following is the test sample I've been working with using Python 3.8.6:
from PySide2.QtWidgets import *
import sys
class MyFormLayout(QFormLayout):
def __init__(self, *args, **kwargs):
super(MyFormLayout, self).__init__(*args, **kwargs)
self.cache = []
print(f"Formlayout's identity: {self=}\nwith parent {self.parent()=}")
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row) ## <-- This seems necessary to make the rowCount() decrement. Alternative?
label_item.widget().setParent(None) ## <-- Runtime Error Here?
field_item.layout().setParent(None)
self.cache.append(label_item.widget(), field_item)
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label_item, field_item
def restoreRow(self, insert_idx: int):
print(f"Called {self.restoreRow.__name__}")
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
to_insert = self.cache.pop()
self.insertRow(insert_idx, to_insert[0], to_insert[1])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
class MyWindow(QWidget):
def __init__(self):
super(MyWindow, self).__init__()
self.mainlay = MyFormLayout(self)
self.cmb = QComboBox()
self.cmb.addItems(["Placeholder", "Remove 1 and 2"])
self.cmb.currentTextChanged.connect(self.remove_rows_via_combo)
self.current_text = self.cmb.currentText()
self.hlay1, self.le1, self.btn1 = self.le_and_btn(placeholderText="1")
self.hlay2, self.le2, self.btn2 = self.le_and_btn(placeholderText="2")
self.hlay3, self.le3, self.btn3 = self.le_and_btn(placeholderText="3")
self.hlay4, self.le4, self.btn4 = self.le_and_btn(placeholderText="4")
self.remove_btn = QPushButton("Remove", clicked=self.remove_row_via_click)
self.restore_btn = QPushButton("Restore", clicked=self.restore_a_row_via_click)
self.mainlay.addRow("Combobox", self.cmb)
for ii, hlayout in zip(range(1, 5), [self.hlay1, self.hlay2, self.hlay3, self.hlay4]):
self.mainlay.addRow(f"Row {ii}", hlayout)
self.mainlay.addRow(self.remove_btn)
self.mainlay.addRow(self.restore_btn)
#staticmethod
def le_and_btn(**kwargs):
hlay, le, btn = QHBoxLayout(), QLineEdit(**kwargs), QPushButton()
hlay.addWidget(le)
hlay.addWidget(btn)
return hlay, le, btn
def remove_row_via_click(self):
self.mainlay.takeRow(1)
def restore_a_row_via_click(self):
self.mainlay.restoreRow(1)
def remove_rows_via_combo(self, text):
print(f"{self.remove_rows_via_combo.__name__} received the text: {text}")
if text == "Remove 1 and 2":
self.mainlay.takeRow(1)
self.mainlay.takeRow(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec_())
I would like to understand why the behavior of the role items is different and how the method may be properly re-implemented.
The problem is that the label was created internally by Qt from a string, rather than by explicitly creating a QLabel in Python. This means that when the row is removed, the last remaining reference is also removed, which deletes the label on the C++ side. After that, all that's left on the Python side is an empty PyQt wrapper - so when you try to call setParent on it, a RuntimeError will be raised, because the underlying C++ part no longer exists.
Your example can therefore be fixed by getting python references to the label/field objects before the layout-item is removed:
class MyFormLayout(QFormLayout):
...
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
# get refs before removal
label = label_item.widget()
field = field_item.layout() or field_item.widget()
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row)
label.setParent(None)
field.setParent(None)
self.cache.append((label, field))
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label, field

Python - AutoComplete in an Editable Gtk.TreeView Cell

I was recently coding PyQt with a QComboBox in a QTable. The QComboBox has autocomplete on by default.
I was wanting to try and reproduce this in Python3 with Gtk3. I came across this example:
Gtk.Entry in Gtk.TreeView (CellRenderer)
that seems to have successfully added an autocompletion to a ComboBox in a Treeview. The example is not complete and I was hoping someone could give me a complete working example. I don't know how I 'connect' the CellRendererAutoComplete class to one of the columns in a treeview.
What I really want is to have AutoCompletion in an editable TreeView cell (with or without a ComboBox), but I have never come across this in the past in Gtk.
Thank you.
Here is a code example:
# https://stackoverflow.com/questions/13756787/
# https://python-gtk-3-tutorial.readthedocs.io/en/latest/cellrenderers.html
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
#######################################################################
class CellRendererAutoComplete(Gtk.CellRendererText):
""" Text entry cell which accepts a Gtk.EntryCompletion object """
__gtype_name__ = 'CellRendererAutoComplete'
def __init__(self, completion):
self.completion = completion
Gtk.CellRendererText.__init__(self)
def do_start_editing(
self, event, treeview, path, background_area, cell_area, flags):
if not self.get_property('editable'):
return
entry = Gtk.Entry()
entry.set_completion(self.completion)
entry.connect('editing-done', self.editing_done, path)
entry.show()
entry.grab_focus()
return entry
def editing_done(self, entry, path):
self.emit('edited', path, entry.get_text())
#######################################################################
class CellRendererTextWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="CellRendererText Example")
self.set_default_size(200, 200)
self.liststore = Gtk.ListStore(str, str)
self.liststore.append(["Fedora", "http://fedoraproject.org/"])
self.liststore.append(["Slackware", "http://www.slackware.com/"])
self.liststore.append(["Sidux", "http://sidux.com/"])
treeview = Gtk.TreeView(model=self.liststore)
renderer_text = Gtk.CellRendererText()
column_text = Gtk.TreeViewColumn("Text", renderer_text, text=0)
treeview.append_column(column_text)
renderer_editabletext = Gtk.CellRendererText()
renderer_editabletext.set_property("editable", True)
########
# This is the problem area, I suppose, but I'm not sure
x = CellRendererAutoComplete()
renderer_editabletext.connect('on_edit',x(renderer_editabletext))
########
column_editabletext = Gtk.TreeViewColumn("Editable Text",renderer_editabletext, text=1)
treeview.append_column(column_editabletext)
renderer_editabletext.connect("edited", self.text_edited)
self.add(treeview)
def text_edited(self, widget, path, text):
self.liststore[path][1] = text
win = CellRendererTextWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
So here is an example showing a CellRendererText and a CellRendererCombo using an EntryCompletion.
Because of the complexity of the Treeview, where one ListStore can be the model behind the Completion, Combo, and Entry, and another model can be behind the Treeview, you should have a very good grasp of Gtk.Treeview to understand this example. Note that this example only uses one Liststore, and only one editable column used by both the CellRendererText and the CellRendererColumn. This makes the example confusing, but it the simplest I can come up with since I do not know the intended use for this Treeview.
#!/usr/bin/python
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class CellRendererTextWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="CellRendererText Example")
self.set_default_size(200, 200)
self.liststore = Gtk.ListStore(str, str)
self.liststore.append(["Fedora", "http://fedoraproject.org/"])
self.liststore.append(["Slackware", "http://www.slackware.com/"])
self.liststore.append(["Sidux", "http://sidux.com/"])
treeview = Gtk.TreeView(model=self.liststore)
self.completion = Gtk.EntryCompletion(model = self.liststore)
self.completion.set_text_column(1)
self.completion.connect('match-selected', self.renderer_match_selected)
renderer_text = Gtk.CellRendererText()
column_text = Gtk.TreeViewColumn("Text", renderer_text, text=0)
treeview.append_column(column_text)
######## CellRendererText with EntryCompletion example
renderer_text = Gtk.CellRendererText()
renderer_text.connect('editing-started', self.renderer_text_editing_started)
renderer_text.connect('edited', self.text_edited)
renderer_text.set_property("editable", True)
column_text_autocomplete = Gtk.TreeViewColumn("Editable Text", renderer_text, text=1)
treeview.append_column(column_text_autocomplete)
######## CellRendererCombo with EntryCompletion example
renderer_combo = Gtk.CellRendererCombo(model = self.liststore)
renderer_combo.set_property("text-column", 1)
renderer_combo.connect('editing-started', self.renderer_combo_editing_started)
renderer_combo.connect('changed', self.combo_changed)
renderer_combo.set_property("editable", True)
column_combo_autocomplete = Gtk.TreeViewColumn("Editable Combo", renderer_combo, text=1)
treeview.append_column(column_combo_autocomplete)
self.add(treeview)
def renderer_match_selected (self, completion, model, tree_iter):
''' beware ! the model and tree_iter passed in here are the model from the
EntryCompletion, which may or may not be the same as the model of the Treeview '''
text_match = model[tree_iter][1]
self.liststore[self.path][1] = text_match
def renderer_text_editing_started (self, renderer, editable, path):
''' since the editable widget gets created for every edit, we need to
connect the completion to every editable upon creation '''
editable.set_completion(self.completion)
self.path = path # save the path for later usage
def text_edited(self, widget, path, text):
self.liststore[path][1] = text
def renderer_combo_editing_started (self, renderer, combo, path):
''' since the editable widget gets created for every edit, we need to
connect the completion to every editable upon creation '''
editable = combo.get_child() # get the entry of the combobox
editable.set_completion(self.completion)
self.path = path # save the path for later usage
def combo_changed (self, combo, path, tree_iter):
''' the path is from the treeview and the tree_iter is from the model
of the combobox which may or may not be the same model as the treeview'''
combo_model = combo.get_property('model')
text = combo_model[tree_iter][1]
self.liststore[path][1] = text
win = CellRendererTextWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
In the official docs, it is explicitly stated that the purpose of editing-started is to add a EntryCompletion and etc. I also subclassed Gtk.CellRendererText before I found that little hint in the docs.

PyQt ToolTip for QTreeView

Please axplain how to enable and show a tooltip for each item in QTreeView. I found a sample of code class TreeModel(QAbstractItemModel) but due to my beginner's level I can't understand how to apply it to my needs.
Data for tooltip should be taken from value of key "note" in dictionary data_for_tree.
#!/usr/bin/env python -tt
# -*- coding: utf-8 -*-
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
reload(sys)
sys.setdefaultencoding('utf8')
data_for_tree = {"tomato":{"color":"red","ammount":"10", "note":"a note for tomato"},"banana":{"color":"yellow","ammount":"1", "note":"b note for banana"}, "some fruit":{"color":"unknown","ammount":"100", "note":"some text"}}
class TreeModel(QAbstractItemModel):
def data(self, index, role=Qt.DisplayRole):
#...
if role == Qt.ToolTipRole:
return 'ToolTip'
def flags(self, index):
if not index.isValid():
return Qt.NoItemFlags # 0
return Qt.ItemIsSelectable # or Qt.ItemIsEnabled
class ProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super(ProxyModel, self).__init__(parent)
def lessThan(self, left, right):
leftData = self.sourceModel().data(left)
rightData = self.sourceModel().data(right)
try:
return float(leftData) < float(rightData)
except ValueError:
return leftData < rightData
class MainFrame(QWidget):
def __init__(self):
QWidget.__init__(self)
self.MyTreeView = QTreeView()
self.MyTreeViewModel = QStandardItemModel()
self.MyTreeView.setModel(self.MyTreeViewModel)
self.most_used_cat_header = ['Name', "ammount", "color"]
self.MyTreeViewModel.setHorizontalHeaderLabels(self.most_used_cat_header)
self.MyTreeView.setSortingEnabled(True)
self.MyTreeView_Fill()
MainWindow = QHBoxLayout(self)
MainWindow.addWidget(self.MyTreeView)
self.setLayout(MainWindow)
def MyTreeView_Fill(self):
for k in data_for_tree:
name = QStandardItem(k)
ammount = QStandardItem(data_for_tree[k]["ammount"])
note = QStandardItem(data_for_tree[k]["color"])
tooltip = data_for_tree[k]["note"]
item = (name, ammount, note)
self.MyTreeViewModel.appendRow(item)
self.MyTreeView.sortByColumn(1, Qt.DescendingOrder)
proxyModel = ProxyModel(self)
proxyModel.setSourceModel(self.MyTreeViewModel)
self.MyTreeView.setModel(proxyModel)
c = 0
while c < len(self.most_used_cat_header):
self.MyTreeView.resizeColumnToContents(c)
c=c+1
if __name__ == "__main__":
app = QApplication(sys.argv)
main = MainFrame()
main.show()
main.move(app.desktop().screen().rect().center() - main.rect().center())
sys.exit(app.exec_())
As you are using the QStandardItem and QStandardItemModel classes (which is what I would recommend!) you don't need to bother with the TreeModel class you have found. Creating your own model is rarely necessary, but for some reason tutorials often encourage you to do so. If you find something encouraging you to subclass QAbstractItemModel, I suggest you check on stack overflow first to see if there is a simpler way to do it! In this case, there is a very simple way to add your tooltips.
If you look at the C++ documentation (which I often find more useful than the PyQt documentation for finding out what methods are available), you will see that QStandardItem has a method called setToolTip().
So all you need to do is call this method on each of the items you add to the model. For example, inside the loop in the MyTreeView_Fill method:
name = QStandardItem(k)
ammount = QStandardItem(data_for_tree[k]["ammount"])
note = QStandardItem(data_for_tree[k]["color"])
tooltip = data_for_tree[k]["note"]
name.setToolTip(tooltip)
ammount.setToolTip(tooltip)
note.setToolTip(tooltip)
Here I've set the tooltip to be the same for every cell in the row (name, amount and note) but you could easily change this to have a different tooltip for one of the cells (hopefully it is obvious how to do that)

Resources