How to customize Qtreewidget item editor in PyQt5? - python

I am making a QtreeWidget with item editable,but the problem is with the Item Editor or QAbstractItemDelegate(might be called like this,not sure).I am unable to change the stylesheet,actually i dont know how to do this.And also i want the selected lines(blue in editor) should be according to my wish.like below picture
here i want that blue selected line upto ".jpg",so that anyone cant change that ".jpg". Only ,one can change upto this".jpg"
Here is my code:
import sys
from PyQt5 import QtCore, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton('Edit')
self.button.clicked.connect(self.edittreeitem)
self.tree = QtWidgets.QTreeWidget()
self.tree.setStyleSheet('background:#333333;color:grey')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tree)
layout.addWidget(self.button)
columns = 'ABCDE'
self.tree.setColumnCount(len(columns))
for index in range(50):
item=QtWidgets.QTreeWidgetItem(
self.tree, [f'{char}{index:02}.jpg' for char in columns])
item.setFlags(item.flags()|QtCore.Qt.ItemIsEditable)
def edittreeitem(self):
getSelected = self.tree.selectedItems()
self.tree.editItem(getSelected[0],0)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setWindowTitle('Test')
window.setGeometry(800, 100, 540, 300)
window.show()
sys.exit(app.exec_())

You can create your own delegate that only considers the base name without the extension, and then set the data using the existing extension.
class BaseNameDelegate(QtWidgets.QStyledItemDelegate):
def setEditorData(self, editor, index):
editor.setText(QtCore.QFileInfo(index.data()).completeBaseName())
def setModelData(self, editor, model, index):
name = editor.text()
if not name:
return
suffix = QtCore.QFileInfo(index.data()).suffix()
model.setData(index, '{}.{}'.format(name, suffix))
class Window(QtWidgets.QWidget):
def __init__(self):
# ...
self.tree.setItemDelegate(BaseNameDelegate(self.tree))
The only drawback of this is that the extension is not visible during editing, but that would require an implementation that is a bit more complex than that, as QLineEdit (the default editor for string values of a delegate) doesn't provide such behavior.

Related

The layout is incorrect after remove widget

I am implement my project using pyqt5. Currently, I have a window including many widget. Now, I want to remove some widgets. The window looks like:
Now, I want to remove the 'name1' widget including the QLabel and QPushButton.
However, after removing all 'name1' widgets, the 'name2' widgets including QLabel and QPushButton can not self-adapte with the window, like:
All my code is:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class Window(QDialog):
def __init__(self):
super().__init__()
self.initGUI()
self.show()
def initGUI(self):
layout = QVBoxLayout()
self.setLayout(layout)
removeLayout = QHBoxLayout()
self.__removeText = QLineEdit()
self.__removeBtn = QPushButton('Remove')
self.__removeBtn.clicked.connect(self.remove)
removeLayout.addWidget(self.__removeText)
removeLayout.addWidget(self.__removeBtn)
ROIsLayout = QVBoxLayout()
for name in ['name1', 'name2']:
subLayout = QHBoxLayout()
subText = QLabel(name)
subText.setObjectName(name)
subBtn = QPushButton(name)
subBtn.setObjectName(name)
subLayout.addWidget(subText)
subLayout.addWidget(subBtn)
ROIsLayout.addLayout(subLayout)
layout.addLayout(removeLayout)
layout.addLayout(ROIsLayout)
self.__ROIsLayout = ROIsLayout
def remove(self, checked=False):
name = self.__removeText.text()
while True:
child = self.__ROIsLayout.takeAt(0)
if child == None:
break
while True:
subChild = child.takeAt(0)
if subChild == None:
break
obName = subChild.widget().objectName()
if name == obName:
widget = subChild.widget()
widget.setParent(None)
child.removeWidget(widget)
self.__ROIsLayout.removeWidget(widget)
del widget
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
update:
Actually, the issue may be the takeAt. The following code is workable:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class Window(QDialog):
def __init__(self):
super().__init__()
self.initGUI()
self.show()
def initGUI(self):
layout = QVBoxLayout()
self.setLayout(layout)
removeLayout = QHBoxLayout()
self.__removeText = QLineEdit()
self.__removeBtn = QPushButton('Remove')
self.__removeBtn.clicked.connect(self.remove)
removeLayout.addWidget(self.__removeText)
removeLayout.addWidget(self.__removeBtn)
ROIsLayout = QVBoxLayout()
for name in ['name1', 'name2']:
subLayout = QHBoxLayout()
subLayout.setObjectName(name)
subText = QLabel(name, parent=self)
subText.setObjectName(name)
subBtn = QPushButton(name, parent=self)
subBtn.setObjectName(name)
subLayout.addWidget(subText)
subLayout.addWidget(subBtn)
ROIsLayout.addLayout(subLayout)
print(name, subLayout, subText, subBtn)
layout.addLayout(removeLayout)
layout.addLayout(ROIsLayout)
self.__ROIsLayout = ROIsLayout
self.record = [subLayout, subText, subBtn]
def remove(self, checked=False):
layout = self.record[0]
txt = self.record[1]
btn = self.record[2]
layout.removeWidget(txt)
txt.setParent(None)
txt.deleteLater()
layout.removeWidget(btn)
btn.setParent(None)
btn.deleteLater()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
But, I have printed the QLabel/QPushButton in the self.record, and I find it is the same with that from child.takeAt(0).widget().
The main issue in your code is that you're constantly using takeAt(). The result is that all items in the __ROIsLayout layout will be removed from it (but not deleted), which, in your case, are the sub layouts. This is clearly not a good approach: only the widgets with the corresponding object name will be actually deleted, while the others will still be "owned" by their previous parent, will still be visible at their previous position and their geometries won't be updated since they're not managed by the layout anymore.
There are multiple solutions to your question, all depending on your needs.
If you need to remove rows from a layout, I'd consider setting the object name on the layout instead, and look for it using self.findChild().
Also consider that, while Qt allows setting the same object name for more than one object, that's not suggested.
Finally, while using del is normally enough, it's usually better to call deleteLater() for all Qt objects, which ensures that Qt correctly removes all objects (and related parentship/connections).
Another possibility, for this specific case, is to use a QFormLayout.

Dynamically Resize QListView Item's Icon

How can I make it so the icon of a QListView's item resizes when i change the slider? It appears to resize the item but not it's icon.
I tried calling both of these, neither of them worked. I would ideally like to not call setGridSize since that causes the widget to ignore the setSpacing(5) which i intend on using.
self.uiListView.setGridSize(iconSize)
self.uiListView.setIconSize(iconSize)
import os, sys, re
from Qt import QtWidgets, QtGui, QtCore
from . import StyleUtils
values = ['MomBod','Colonel','Tater','Tot','Ginger','Donut','Sport','LaLa','Itchy','Bruiser','Cotton','Cumulus','Toodles','Salt','Ghoulie','Cat','Dirty','Harry','Buckeye','Flyby','Swiss','Miss','Buddy','Pecan','Sunny','Jet','Thor','Gingersnap','Cuddle','Pig','Turkey','Foxy','Mini','Me','Dolly','Stud','Music','Man','Barbie','Munchkin','Bubba','Hammer','Twizzler','Bebe']
class ViewerWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.resize(500,500)
self.uiIconSize = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.uiIconSize.setRange(32,256)
self.uiIconSize.setValue(128)
self.uiIconSize.setMinimumWidth(100)
self.viewerModel = QtGui.QStandardItemModel()
self.viewerProxyModel = QtCore.QSortFilterProxyModel()
self.viewerProxyModel.setSourceModel(self.viewerModel)
self.uiListView = QtWidgets.QListView()
self.uiListView.setSpacing(5)
self.uiListView.setMovement(QtWidgets.QListView.Static)
self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setLayoutMode(QtWidgets.QListView.Batched)
self.uiListView.setBatchSize(100)
self.uiListView.setFlow(QtWidgets.QListView.LeftToRight)
self.uiListView.setWrapping(True)
self.uiListView.setResizeMode(QtWidgets.QListView.Adjust)
self.uiListView.setDragEnabled(False)
self.uiListView.setUniformItemSizes(True)
self.uiListView.setIconSize(self.getIconSize())
self.uiListView.setSelectionMode(QtWidgets.QListView.ExtendedSelection)
self.uiListView.setModel(self.viewerProxyModel)
# main layout
self.layout = QtWidgets.QVBoxLayout()
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.layout.addWidget(self.uiListView)
self.layout.addWidget(self.uiIconSize)
self.setLayout(self.layout)
# Signals
self.uiIconSize.valueChanged.connect(self.setItemSize)
# Init
self.populateModel()
def getIconSize(self):
return QtCore.QSize(self.uiIconSize.value(), self.uiIconSize.value())
def setItemSize(self):
iconSize = self.getIconSize()
# self.uiListView.setGridSize(iconSize)
self.uiListView.setIconSize(iconSize)
def populateModel(self):
model = self.viewerModel
model.clear()
icon = QtGui.QIcon('C:/Users/jmartini/Desktop/image.png')
for x in values:
newItem = QtGui.QStandardItem(x)
newItem.setData(icon, role=QtCore.Qt.DecorationRole)
model.appendRow(newItem)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = ViewerWidget()
ex.show()
sys.exit(app.exec_())
I discovered this is a result of my icon's native size being 64x64. Qt will not resize the icon to be larger than it's original size. It will only resize the icon to be of a smaller size.
self.uiListView.setIconSize(QtCore.QSize(64,64))

python GUI autocomplete by key value

I am trying to create an autocomplete GUI in python such that as I type a first name, I see possible last names. For example, let's say I have this dictionary: {"George": ["Washington", "Bush"]}. When I start typing "G", I want it to show "Washington" and "Bush". When "Washington" is selected, I want "Washington" to show. I am new to GUIs and I think PyQt has an example of autocompletion, but the words are not in key value pairs but a list of words.
https://wiki.python.org/moin/PyQt/Adding%20auto-completion%20to%20a%20QLineEdit
Is there a way to edit the code in the link so that I can enable this feature? Thank you!
You have to override the pathFromIndex method so that when you select some text, the appropriate option is written in the QLineEdit, and to change what is shown in the popup a delegate should be used.
from PyQt5 import QtCore, QtGui, QtWidgets
def create_model(d):
model = QtGui.QStandardItemModel()
for key, value in d.items():
for val in value:
it = QtGui.QStandardItem(key)
it.setData(val, QtCore.Qt.UserRole)
model.appendRow(it)
return model
class StyledItemDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super(StyledItemDelegate, self).initStyleOption(option, index)
option.text = index.data(QtCore.Qt.UserRole)
class Completer(QtWidgets.QCompleter):
def __init__(self, parent=None):
super(Completer, self).__init__(parent)
QtCore.QTimer.singleShot(0, self.change_delegate)
#QtCore.pyqtSlot()
def change_delegate(self):
delegate = StyledItemDelegate(self)
self.popup().setItemDelegate(delegate)
def pathFromIndex(self, index):
return index.data(QtCore.Qt.UserRole)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
d = {
"George": ["Washington", "Bush"],
"Languages": ["Python", "C++"]
}
model = create_model(d)
w = QtWidgets.QLineEdit()
completer = Completer(w)
completer.setModel(model)
w.setCompleter(completer)
w.show()
sys.exit(app.exec_())

Get the place in one Qtreewidget and expand another to the same place

I have two QTreeWidgets in my QT app, using python (PyQt4).
I want to
Manually expand TreeWidget 1 to an item and then.
If this item is in TreeWidget 2, make TreeWidget 2 expand to the same item.
The reason is I have 2 tabs each have a treewidget.
You will need to excuse me, I'm not an experienced programmer and have been struggling.
Thanks
The question is a little light on details, since it doesn't specify what counts as the "same" item, and doesn't state which items can be expanded.
However, this simple demo script should provide a reasonable starting point:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.tree1 = QtGui.QTreeWidget(self)
self.tree2 = QtGui.QTreeWidget(self)
layout = QtGui.QHBoxLayout(self)
for tree in (self.tree1, self.tree2):
tree.header().hide()
tree.itemExpanded.connect(self.handleExpanded)
tree.itemCollapsed.connect(self.handleCollapsed)
for text in 'one two three four'.split():
item = QtGui.QTreeWidgetItem(tree, [text])
for text in 'red blue green'.split():
child = QtGui.QTreeWidgetItem(item, [text])
layout.addWidget(tree)
def handleExpanded(self, item):
self.syncExpansion(item, True)
def handleCollapsed(self, item):
self.syncExpansion(item, False)
def syncExpansion(self, item, expand=True):
if item is not None:
tree = item.treeWidget()
if tree is self.tree1:
tree = self.tree2
else:
tree = self.tree1
text = item.text(0)
for other in tree.findItems(text, QtCore.Qt.MatchFixedString):
other.setExpanded(expand)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(300, 500, 300, 300)
window.show()
sys.exit(app.exec_())

How do you set the column width on a QTreeView?

Bear with me, I'm still new to QT and am having trouble wrapping my brain around how it does things.
I've created and populated a QTreeView with two columns:
class AppForm(QMainWindow):
def __init__(self, parent = None):
super(AppForm, self).__init__(parent)
self.model = QStandardItemModel()
self.view = QTreeView()
self.view.setColumnWidth(0, 800)
self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.view.setModel(self.model)
self.setCentralWidget(self.view)
Everything's working great, except the columns are extremely narrow. I hoped that setColumnWidth(0, 800) would widen the first column, but it doesn't seem to be having any effect. What's the proper method for setting column widths?
When you call setColumnWidth, Qt will do the equivalent of:
self.view.header().resizeSection(column, width)
Then, when you call setModel, Qt will (amongst other things) do the equivalent of:
self.view.header().setModel(model)
So the column width does get set - just not on the model the tree view ends up with.
tl;dr: set the column width after you set the model.
EDIT
Here's a simple demo script based on your example:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.model = QtGui.QStandardItemModel()
self.view = QtGui.QTreeView()
self.view.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.view.setModel(self.model)
self.setCentralWidget(self.view)
parent = self.model.invisibleRootItem()
for item in 'One Two Three Four'.split():
parent.appendRow([
QtGui.QStandardItem(item),
QtGui.QStandardItem(),
QtGui.QStandardItem(),
])
self.view.setColumnWidth(0, 800)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
self.view.resizeColumnToContents(0)
This makes sure that given column's width and height are set to match with content.

Categories