I am creating a custom qtreewidget class that autoresizes its window to fit exactly the currently visible elements (I don't want to have to scroll). To do this, I run a count function to find the number of open qtreewidgetitems and their children and set a fixed height from there. However, when I expand a child widget (click the expand arrow on one of my items) the whole view suddenly needs to scroll because there is extra white space at the bottom, despite my count function accurately calculating the needed height. How do I get rid of it?
Below is a working class that can be run directly as is.
import sys
from PyQt4 import QtGui
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class TreeWidget(QTreeWidget):
def __init__(self, parent=None):
super(TreeWidget, self).__init__()
self.installEventFilter(self)
self.setStyleSheet('''
background: None;
border: None;
outline: None;
outline-width: 0px;
selection-background-color: blue;
''')
header = QtGui.QTreeWidgetItem(["Tree", "First"])
self.setAutoScroll(False)
self.setHeaderItem(header)
self.header().close()
# self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
def fill_item(self, item, value):
item.setExpanded(False)
if type(value) is dict:
for key, val in sorted(value.iteritems()):
child = QTreeWidgetItem()
child.setText(0, unicode(key))
# child.doubleClicked.connect(lambda: self.doubleClicked1(child))
item.addChild(child)
self.fill_item(child, val)
elif type(value) is list:
last = None
for val in value:
child = QTreeWidgetItem()
if type(val) is dict:
item.addChild(child)
child.setText(0, '[dict]')
# child.doubleClicked.connect(lambda: self.doubleClicked1(child))
self.fill_item(child, val)
last = child
elif type(val) is list:
self.fill_item(last, val)
else:
item.addChild(child)
child.setText(0, unicode(val))
child.setText(1, 'test')
# child.doubleClicked.connect(lambda: self.doubleClicked1(child))
child.setExpanded(False)
last = child
else:
child = QTreeWidgetItem()
child.setText(0, unicode(val))
child.setText(1, 'test')
# child.doubleClicked.connect(lambda: self.doubleClicked1(child))
item.addChild(child)
def fill_widget(self, value):
self.clear()
self.fill_item(self.invisibleRootItem(), value)
def resizeEvent(self, event):
self.resize()
def resize(self):
width = 50
self.header().resizeSection(1, width)
self.header().resizeSection(0, self.width()-width)
height = self.visibleCount()
print height/15
self.setFixedHeight(height+0)
def eventFilter(self, source, event):
if source is self:
if event.type() == 1:
self.resize()
elif event.type() == QEvent.Leave:
self.clearSelection()
return QtGui.QTreeWidget.eventFilter(self, source, event)
def visibleCount(self, parent=0):
height = 0
if parent == 0:
topHeight = 0
for a in xrange(self.topLevelItemCount()):
item = self.topLevelItem(a)
topHeight += self.visualItemRect(item).height()
if item.isExpanded():
height += self.visibleCount(item)
height += topHeight
else:
childHeight = 0
for a in xrange(parent.childCount()):
item = parent.child(a)
childHeight += self.visualItemRect(item).height()
if item.isExpanded():
height += self.visibleCount(item)
height += childHeight
return height
def editClicked(self, parent=0):
# print 'edit 2'
if parent == 0:
for a in xrange(self.topLevelItemCount()):
item = self.topLevelItem(a)
print type(item)
item.setExpanded(True)
self.editClicked(item)
else:
for a in xrange(parent.childCount()):
item = parent.child(a)
print type(item)
item.setText(1, '+')
item.setExpanded(True)
self.editClicked(item)
def doubleClicked1(self, widget):
print widget
def main():
app = QtGui.QApplication(sys.argv)
ex = TreeWidget()
data = [
'Make sure ZMQ remote button gets honored',
'Fill in plot',
'Remove cycle',
'Remove current control or make it working',
'Handle possible startup errors with dialogs',
'Get improved current read-out (requires hardware changes)',
['1','2','3'],
'Email quench notification'
]
ex.fill_widget(data)
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You can remove the event filter entirely. You need to resize the widget when the items are expanded or collapsed. Both actions trigger signals, so you can simply connect those signals to resize:
class TreeWidget(QTreeWidget):
def __init__(self, parent=None):
super(TreeWidget, self).__init__(parent)
self.expanded.connect(self.resize)
self.collapsed.connect(self.resize)
Related
I'm struggling to get the QRubberBand to work with masks. Basically I have a background image and then I have subclassed a QLabelButton. I have attached a mask to the QLabelButton to be used like a button. I want to be able to select the button when I drag out a QRect.
I've got the dragging to work fine and I can click my "button", but when I release, I don't know how to tell it to act only upon the mask of the QLabelButton. Instead it returns the QLabelButton itself and because it takes up the whole window, it is selected anywhere I drag, but in fact I want it to see if it intersects with my mask. I'd appreciate any help and pointers I can get in this matter.
Edit: Here's where I got the great code for being able to move the QRect:
How to get my selection box to move properly using PySide2?
And here's where I got the code to get the QLabel upon release:
https://github.com/hzhaoaf/CTDiagnosis/blob/master/GUI/QRubberBand.py
In resources you will find a background image of Super Mario and his mask.
resources
from maya import OpenMayaUI
import pymel.core as pm
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtGui import QPixmap, QIcon
from shiboken2 import wrapInstance
import urllib
class QLabelButton(QtWidgets.QLabel):
def mouseReleaseEvent(self, ev):
self.emit(QtCore.SIGNAL('clicked()'))
def main_maya_window():
main_window_ptr = OpenMayaUI.MQtUtil.mainWindow()
return wrapInstance(long(main_window_ptr), QtWidgets.QWidget)
class rubberBandUI(QtWidgets.QDialog):
def __init__(self, parent=main_maya_window()):
super(rubberBandUI, self).__init__(parent)
imgRes = [512, 512]
self.setMinimumSize(imgRes[0], imgRes[1])
self.setMaximumSize(imgRes[0], imgRes[1])
# resources
bgPath = r"C://bg.png"
maskPath = r"C://bg_mask.png"
# background image
self.bg = QtWidgets.QLabel('bg', self)
self.bg.setPixmap(QtGui.QPixmap(bgPath))
# mask image
self.mask = QLabelButton('mask', self)
self.mask.setGeometry(0, 0, imgRes[0], imgRes[1])
self.mask.setAlignment(QtCore.Qt.AlignCenter)
self.mask.setMask(maskPath)
self.connect(self.mask, QtCore.SIGNAL('clicked()'), lambda: self.onClick(self.mask) )
self.mask.setStyleSheet("""QLabel:hover{background-color: rgba(64, 128, 255, 180); border: 1px solid blue;}""")
# create rubber band selection
self.rubberBand = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle, self)
# Create variables that will be handling the moving logic.
self.sourceGeo = None
self.altPoint = None
self.delta = None
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.origin = event.pos()
self.drag_selection = QtCore.QRect(self.origin, QtCore.QSize())
self.rubberBand.setGeometry(self.drag_selection)
self.rubberBand.show()
# Must implement this event to detect when alt gets released
def keyReleaseEvent(self, event):
if event.key() == QtCore.Qt.Key_Alt:
if self.delta is not None:
# This is important: Add delta to origin so that it shifts it over.
# This is needed if the user repeatedly pressed alt to move it, otherwise it would pop.
self.origin += self.delta
# Reset the rest of the variables.
self.sourceGeo = None
self.altPoint = None
self.delta = None
def mouseMoveEvent(self, event):
if event.modifiers() == QtCore.Qt.AltModifier:
# Get the point where alt is pressed and the selection's current geometry.
if self.altPoint is None:
self.sourceGeo = self.rubberBand.geometry()
self.altPoint = event.pos()
self.delta = event.pos() - self.altPoint # Calculate difference from the point alt was pressed to where the cursor is now.
newGeo = QtCore.QRect(self.sourceGeo) # Create a copy
newGeo.moveTopLeft(self.sourceGeo.topLeft() + self.delta) # Apply the delta onto the geometry to move it.
self.rubberBand.setGeometry(newGeo) # Move the selection!
else:
self.drag_selection = QtCore.QRect(self.origin, event.pos()).normalized()
self.rubberBand.setGeometry(self.drag_selection)
def mouseReleaseEvent(self, event):
if self.rubberBand.isVisible():
self.rubberBand.hide()
selected = []
rect = self.rubberBand.geometry()
#self.cropImage(rect)
for child in self.findChildren(QLabelButton):
if rect.intersects(child.geometry()):
selected.append(child)
print 'Selection Contains:\n ',
if selected:
print ' '.join(
'Button: %s\n' % child.text() for child in selected)
else:
print ' Nothing\n'
# label click function to print its name
def onClick(self, button):
print '%s clicked' % button.text()
if __name__ == '__main__':
app = QtWidgets.QApplication.instance()
if app == None:
app = QtWidgets.QApplication([])
win = rubberBandUI()
#Set WA_DeleteOnClose attribute
win.setAttribute(QtCore.Qt.WA_DeleteOnClose)
win.show()
app.exec_()
sys.exit()
I am trying to make a simple input form that accepts certain inputs and shows output in a table.
As per the code, I get the following output.
But I am looking for the following output.
Following is the code snippet that I have attempted.
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui
from PyQt5.QtGui import *
from PyQt5.QtCore import *
# Subclass QMainWindow to customize your application's main window
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("Check Distance Travelled By Vehicles")
self.vehicleNamelbl = QLabel('VehicleName : ')
self.vehicleNamecombo = QComboBox()
self.vehicleNamecombo.addItem('SwitftDzire')
self.dateEdit = QDateEdit()
self.dateEdit.__init__(calendarPopup=True)
self.dateEdit.setDateTime(QtCore.QDateTime.currentDateTime())
self.dateEdit.editingFinished.connect(lambda: date_method())
self.petrolCB = QCheckBox('Petrol')
self.petrolCB.setChecked(True)
self.dieselCB = QCheckBox('Diesel')
self.dieselCB.setChecked(False)
self.petrolCB.stateChanged.connect(self.checkpetrolCB)
self.dieselCB.stateChanged.connect(self.checkdieselCB)
self.submitBtn = QPushButton('Submit ')
# adding action to the date when enter key is pressed
self.submitBtn.clicked[bool].connect(self.collecInput)
grid = QGridLayout()
grid.setSpacing(10)
grid.addWidget(self.vehicleNamelbl,1,0)
grid.addWidget(self.vehicleNamecombo,1,1)
grid.addWidget(self.dateEdit,1,2)
grid.addWidget(self.petrolCB,1,3)
grid.addWidget(self.dieselCB,1,4)
grid.addWidget(self.submitBtn,1,5)
# geometry of the main window
qtRectangle = self.frameGeometry()
# center point of screen
centerPoint = QDesktopWidget().availableGeometry().center()
# move rectangle's center point to screen's center point
qtRectangle.moveCenter(centerPoint)
# top left of rectangle becomes top left of window centering it
self.move(qtRectangle.topLeft())
self.setLayout(grid)
self.show()
# method called by date edit
def date_method():
print('Inside date_method')
# getting the date
value = self.dateEdit.date()
print(value)
print(value.toPyDate())
def checkpetrolCB(self,checked):
if checked :
print('Petrol Vehicle Is Selected')
self.OdFlag = 1
else:
print('Petrol Vehicle Is De-Selected')
def checkdieselCB(self,checked):
if checked :
print('Diesel Vehicle Is Selected')
self.OdFlag = 2
else:
print('Diesel Vehicle Is De-Selected')
def collecInput(self) :
print('Submit Button Pressed!! Inputs Selected')
print(self.vehicleNamecombo.currentText())
print(self.dateEdit.date().toPyDate())
if self.petrolCB.isChecked() == True and self.dieselCB.isChecked() == False :
print('Petrol Vehicle Is Selected')
if self.dieselCB.isChecked() == True and self.petrolCB.isChecked() == False :
print('Diesel Vehicle Is Selected')
if self.petrolCB.isChecked() == True and self.dieselCB.isChecked() == True :
print('Both Petrol And Diesel Vehicle Are Selected')
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Critical)
msgBox.setText('Select Either Petrol Or Diesel')
msgBox.setWindowTitle("Alert PopUp")
returnValue = msgBox.exec_()
return
# Call A Module To Get The List Of Files Present As per The Input
vehicleFileList = [ 'dist_SwitftDzire_CityA.13OCT2020','dist_SwitftDzire_CityB.13OCT2020','dist_SwitftDzire_CityC.13OCT2020']
print('Back to collecInput')
print(vehicleFileList)
print('Num Of Files Found : '+str(len(vehicleFileList)))
numOfFiles = len(vehicleFileList)
if numOfFiles == 0 : # No Files Have Been Detected
print('No Files Found')
msgBox = QMessageBox()
msgBox.setIcon(QMessageBox.Critical)
msgBox.setText('No Files Found')
msgBox.setWindowTitle("Alert PopUp")
returnValue = msgBox.exec_()
else: # Atleast 1 File Is Detected
print('Populating table entries')
table = MyTable(vehicleFileList, numOfFiles, 2, self)
# add Qt.Window to table's flags
table.setWindowFlags(table.windowFlags() | Qt.Window)
table.show()
class MyTable(QTableWidget):
def __init__(self, vehicleFileList, *args):
QTableWidget.__init__(self, *args)
self.data = vehicleFileList
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.resizeColumnsToContents()
self.resizeRowsToContents()
self.horizontalHeader().setStretchLastSection(False)
self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.setHorizontalHeaderLabels(['Available Vehicle Data Files','Missing Files'])
print('Inside MyTable')
print(vehicleFileList)
rowCount=0
for item in vehicleFileList :
print(item)
self.setItem(rowCount,0,QTableWidgetItem(item))
rowCount+=1
def main():
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
What changes do I need to make to get the window positioning of my choice?
Note: The second window is not a child of the first window.
The idea is to calculate the geometry of the first window and use that information to modify the geometry of the second window:
table = MyTable(vehicleFileList, numOfFiles, 2, self)
# add Qt.Window to table's flags
table.setWindowFlags(table.windowFlags() | Qt.Window)
table.resize(self.width(), table.sizeHint().height())
table.window().move(self.geometry().bottomLeft())
table.show()
what I need in this case is "uncheck or check" the items in QListView with mouse hold and press
I've added Items with check boxes , I need to change the state of the items (check or uncheck) for multi items with just press and hold , like making multi selection .
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class items_List(QListView):
clicked = pyqtSignal()
def __init__(self,items):
QListView.__init__(self)
#self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.model = QStandardItemModel()
self.setModel(self.model)
self.model.itemChanged.connect(self.on_itemChanged)
self.items = items
self.add_items_(self.items)
self.show()
def add_items_(self,all_items=None):
self.active_items = []
if all_items == None:
return
for item in all_items:
#self.model.appendRow(str(i))
self.item = QStandardItem(str(item))
self.item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsSelectable)
self.item.setData(QVariant(Qt.Checked), Qt.CheckStateRole)
self.model.appendRow(self.item)
self.active_items.append(str(item))
def selected_item(self):
print "Selected "
#pyqtSlot(QStandardItem)
def on_itemChanged(self, item):
state = ['UNCHECKED', 'TRISTATE', 'CHECKED'][item.checkState()]
if state == 'UNCHECKED':
self.active_items.remove(item.text())
else:
if item.text() not in self.active_items:
self.active_items.append(str(item.text()))
print self.active_items
def main():
app = QApplication(sys.argv)
items = [1001,1002,1003,1004,1005]
win = items_List(items)
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I have created subclass of QTreeWidget and QTreeWidgetItem where I am trying to highlight the new added items (where the text will be colored red).
The tree hierarchy that I have adopted is as follows:
|-- parent
|--|-- child
|--|-- child
The tree widget is initially populated from a dictionary.
To get the difference, I did it by converting the current hierarchy in the tree widget into a dictionary and have it compared against the initial dictionary that it was populated with.
However if I add in a new child to an existing parent in which the name of the new child already existed in another parent, the same method as mentioned above does not works, as it will colored the first result that it find.
To replicate:
select menuB
right mouse click > add new sub item
input name: a101
hit "Highlight Diff." button
a101 child item under menuA is highlighted instead of the one in menuB
What would be the best way to go in getting the index of newly added child(ren)?
Thank you for any replies.
P.S: If anyone has better suggestion for the parent highlighting, please feel free to chip in.
class CustomTreeWidgetItem(QtGui.QTreeWidgetItem):
def __init__(self, widget=None, text=None, is_tristate=False):
super(CustomTreeWidgetItem, self).__init__(widget)
self.setText(0, text)
if is_tristate:
# Solely for the Parent item
self.setFlags(
self.flags()
| QtCore.Qt.ItemIsTristate
| QtCore.Qt.ItemIsEditable
| QtCore.Qt.ItemIsUserCheckable
)
else:
self.setFlags(
self.flags()
| QtCore.Qt.ItemIsEditable
| QtCore.Qt.ItemIsUserCheckable
)
self.setCheckState(0, QtCore.Qt.Unchecked)
def setData(self, column, role, value):
state = self.checkState(column)
QtGui.QTreeWidgetItem.setData(self, column, role, value)
if (role == QtCore.Qt.CheckStateRole and
state != self.checkState(column)):
tree_widget = CustomTreeWidget()
if tree_widget is not None:
tree_widget.itemToggled.emit(self, column)
class CustomTreeWidget(QtGui.QTreeWidget):
def __init__(self, widget=None):
super(CustomTreeWidget, self).__init__(widget)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.show_custom_menu)
def show_custom_menu(self):
base_node = self.selectedItems()[0]
qmenu = QtGui.QMenu(self)
remove_action = QtGui.QAction("Remove item", self)
remove_action.triggered.connect(self.remove_selected_item)
qmenu.addAction(remove_action)
# The following options are only effected for top-level items
# top-level items do not have `parent()`
if not base_node.parent():
add_new_child_action = QtGui.QAction("Add new sub item", self)
add_new_child_action.triggered.connect(
partial(self.adds_new_child_item, base_node)
)
# qmenu.addAction(add_new_child_action)
qmenu.insertAction(remove_action, add_new_child_action)
qmenu.exec_(QtGui.QCursor.pos())
def add_item_dialog(self, title):
text, ok = QtGui.QInputDialog.getText(
self,
"Add {0} Item".format(title),
"Enter name for {0}-Item:".format(title)
)
if ok and text != "":
return text
def add_new_parent_item(self):
input_text = self.add_item_dialog("Parent")
if input_text:
CustomTreeWidgetItem(self, input_text, is_tristate=True)
def adds_new_child_item(self, base_node):
input_text = self.add_item_dialog("Sub")
if input_text:
CustomTreeWidgetItem(base_node, input_text)
self.setItemExpanded(base_node, True)
def remove_selected_item(self):
root = self.invisibleRootItem()
for item in self.selectedItems():
(item.parent() or root).removeChild(item)
def derive_tree_items(self, mode="all"):
all_items = defaultdict(list)
root_item = self.invisibleRootItem()
top_level_count = root_item.childCount()
for i in range(top_level_count):
top_level_item = root_item.child(i)
top_level_item_name = str(top_level_item.text(0))
child_num = top_level_item.childCount()
all_items[top_level_item_name] = []
for n in range(child_num):
child_item = top_level_item.child(n)
child_item_name = str(child_item.text(0)) or ""
if mode == "all":
all_items[top_level_item_name].append(child_item_name)
elif mode == "checked":
if child_item.checkState(0) == QtCore.Qt.Checked:
all_items[top_level_item_name].append(child_item_name)
elif mode == "unchecked":
if child_item.checkState(0) == QtCore.Qt.Unchecked:
all_items[top_level_item_name].append(child_item_name)
return all_items
class MainApp(QtGui.QWidget):
def __init__(self):
super(MainApp, self).__init__()
# initial dictionary that is populated into the tree widget
test_dict = {
"menuA": ["a101", "a102"],
"menuC": ["c101", "c102", "c103"],
"menuB": ["b101"],
}
self._tree = CustomTreeWidget()
self._tree.header().hide()
for pk, pv in sorted(test_dict.items()):
parent = CustomTreeWidgetItem(self._tree, pk, is_tristate=True)
for c in pv:
child = CustomTreeWidgetItem(parent, c)
self.orig_dict = self._tree.derive_tree_items()
# Expand the hierarchy by default
self._tree.expandAll()
tree_layout = QtGui.QVBoxLayout()
self.btn1 = QtGui.QPushButton("Add new item")
self.btn2 = QtGui.QPushButton("Highlight Diff.")
tree_layout.addWidget(self._tree)
tree_layout.addWidget(self.btn1)
tree_layout.addWidget(self.btn2)
main_layout = QtGui.QHBoxLayout()
main_layout.addLayout(tree_layout)
self.setLayout(main_layout)
self.setup_connections()
def setup_connections(self):
self.btn1.clicked.connect(self.add_parent_item)
self.btn2.clicked.connect(self.highlight_diff)
def add_parent_item(self):
# Get current selected in list widget
# CustomTreeWidgetItem(self._tree, "test", is_tristate=True)
self._tree.add_new_parent_item()
def highlight_diff(self):
self.current_dict = self._tree.derive_tree_items()
if self.orig_dict != self.current_dict:
# check for key difference
diff = [k for k in self.current_dict if k not in self.orig_dict]
if diff:
# get the difference of top-level items
for d in diff:
top_item = self._tree.findItems(d, QtCore.Qt.MatchExactly|QtCore.Qt.MatchRecursive)
#print aaa[0].childCount()
top_item[0].setTextColor(0, QtGui.QColor(255, 0, 0))
if top_item[0].childCount():
for n in range(top_item[0].childCount()):
top_item[0].child(n).setTextColor(0, QtGui.QColor(255, 0, 0))
# to highlight the child diff. of existing top-items
# issue with this portion if the new added item name already existed
for k, v in self.current_dict.items():
if k in self.orig_dict:
diff = set(v).difference(self.orig_dict.get(k), [])
for d in diff:
child_item = self._tree.findItems(d, QtCore.Qt.MatchExactly|QtCore.Qt.MatchRecursive)
child_item[0].setTextColor(0, QtGui.QColor(255, 0, 0))
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = MainApp()
w.show()
sys.exit(app.exec_())
You can save in a role a flag indicating if it is a new item or not and change the color using a delegate:
import sys
from functools import partial
from PyQt4 import QtCore, QtGui
from collections import defaultdict
IsNewItemRole = QtCore.Qt.UserRole + 1000
class CustomTreeDelegate(QtGui.QStyledItemDelegate):
#property
def text_color(self):
if not hasattr(self, "_text_color"):
self._text_color = QtGui.QColor()
return self._text_color
#text_color.setter
def text_color(self, color):
self._text_color = color
def initStyleOption(self, option, index):
super(CustomTreeDelegate, self).initStyleOption(option, index)
if self.text_color.isValid() and index.data(IsNewItemRole):
option.palette.setBrush(QtGui.QPalette.Text, self.text_color)
class CustomTreeWidgetItem(QtGui.QTreeWidgetItem):
def __init__(self, parent=None, text="", is_tristate=False, is_new_item=False):
super(CustomTreeWidgetItem, self).__init__(parent)
self.setText(0, text)
flags = QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsUserCheckable
if is_tristate:
flags |= QtCore.Qt.ItemIsTristate
else:
self.setCheckState(0, QtCore.Qt.Unchecked)
self.setFlags(self.flags() | flags)
self.setData(0, IsNewItemRole, is_new_item)
def setData(self, column, role, value):
state = self.checkState(column)
QtGui.QTreeWidgetItem.setData(self, column, role, value)
if role == QtCore.Qt.CheckStateRole and state != self.checkState(column):
tree_widget = self.treeWidget()
if isinstance(tree_widget, CustomTreeWidget):
tree_widget.itemToggled.emit(self, column)
class CustomTreeWidget(QtGui.QTreeWidget):
itemToggled = QtCore.pyqtSignal(QtGui.QTreeWidgetItem, int)
def __init__(self, widget=None):
super(CustomTreeWidget, self).__init__(widget)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.show_custom_menu)
def show_custom_menu(self, pos):
base_node = self.itemAt(pos)
if base_node is None:
return
qmenu = QtGui.QMenu(self)
remove_action = QtGui.QAction("Remove item", self)
remove_action.triggered.connect(self.remove_selected_item)
qmenu.addAction(remove_action)
# The following options are only effected for top-level items
# top-level items do not have `parent()`
if base_node.parent() is None:
add_new_child_action = QtGui.QAction("Add new sub item", self)
add_new_child_action.triggered.connect(
partial(self.adds_new_child_item, base_node)
)
# qmenu.addAction(add_new_child_action)
qmenu.insertAction(remove_action, add_new_child_action)
qmenu.exec_(self.mapToGlobal(pos))
def add_item_dialog(self, title):
text, ok = QtGui.QInputDialog.getText(
self, "Add {0} Item".format(title), "Enter name for {0}-Item:".format(title)
)
if ok:
return text
def add_new_parent_item(self):
input_text = self.add_item_dialog("Parent")
if input_text:
it = CustomTreeWidgetItem(
self, input_text, is_tristate=True, is_new_item=True
)
def adds_new_child_item(self, base_node):
input_text = self.add_item_dialog("Sub")
if input_text:
it = CustomTreeWidgetItem(base_node, input_text, is_new_item=True)
self.setItemExpanded(base_node, True)
it.setData(0, IsNewItemRole, True)
def remove_selected_item(self):
root = self.invisibleRootItem()
for item in self.selectedItems():
(item.parent() or root).removeChild(item)
class MainApp(QtGui.QWidget):
def __init__(self, parent=None):
super(MainApp, self).__init__(parent)
# initial dictionary that is populated into the tree widget
test_dict = {
"menuA": ["a101", "a102"],
"menuC": ["c101", "c102", "c103"],
"menuB": ["b101"],
}
self._tree = CustomTreeWidget()
self._tree.header().hide()
self._tree_delegate = CustomTreeDelegate(self._tree)
self._tree.setItemDelegate(self._tree_delegate)
for pk, pv in sorted(test_dict.items()):
parent = CustomTreeWidgetItem(self._tree, pk, is_tristate=True)
for c in pv:
child = CustomTreeWidgetItem(parent, c)
# Expand the hierarchy by default
self._tree.expandAll()
tree_layout = QtGui.QVBoxLayout()
self.btn1 = QtGui.QPushButton("Add new item")
self.btn2 = QtGui.QPushButton("Highlight Diff.")
tree_layout.addWidget(self._tree)
tree_layout.addWidget(self.btn1)
tree_layout.addWidget(self.btn2)
main_layout = QtGui.QHBoxLayout(self)
main_layout.addLayout(tree_layout)
self.setup_connections()
def setup_connections(self):
self.btn1.clicked.connect(self.add_parent_item)
self.btn2.clicked.connect(self.highlight_diff)
def add_parent_item(self):
# Get current selected in list widget
# CustomTreeWidgetItem(self._tree, "test", is_tristate=True)
self._tree.add_new_parent_item()
def highlight_diff(self):
self._tree_delegate.text_color = QtGui.QColor(255, 0, 0)
self._tree.viewport().update()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = MainApp()
w.show()
sys.exit(app.exec_())
I found this code snippet of code on SO:
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.tree = QtGui.QTreeWidget(self)
self.tree.setHeaderHidden(True)
for index in range(2):
parent = self.addItem(self.tree, 'Item%d' % index)
for color in 'Red Green Blue'.split():
subitem = self.addItem(parent, color)
for letter in 'ABC':
self.addItem(subitem, letter, True, False)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.tree)
self.tree.itemChanged.connect(self.handleItemChanged)
def addItem(self, parent, text, checkable=False, expanded=True):
item = QtGui.QTreeWidgetItem(parent, [text])
if checkable:
item.setCheckState(0, QtCore.Qt.Unchecked)
else:
item.setFlags(
item.flags() & ~QtCore.Qt.ItemIsUserCheckable)
item.setExpanded(expanded)
return item
def handleItemChanged(self, item, column):
if item.flags() & QtCore.Qt.ItemIsUserCheckable:
path = self.getTreePath(item)
if item.checkState(0) == QtCore.Qt.Checked:
print('%s: Checked' % path)
else:
print('%s: UnChecked' % path)
def getTreePath(self, item):
path = []
while item is not None:
path.append(str(item.text(0)))
item = item.parent()
return '/'.join(reversed(path))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 250, 450)
window.show()
I wonder, how can i hide the decorators from the result, on all of the items?
I know i can hide with setstylesheet doesn't actually removed the arrows, just hide them, which is counterproductive if you accidentally hide them.
item.setChildPolicy(QTreeWidgetItem.DontShowIndicator) either removes the children, or permanently closes them, because the subitems(children of item) all disappear once i do that, and can't do anything with... Tried to expand too, does't work for me.
Actually in PyQt5, so the answer doesn't need to be in PyQt4.
I got a potential solution from here: How do you disable expansion in QTreeWidget/QTreeView?
Using setStyleSheet, as suggested by eyllanesc, to visibly hide the arrow decorator:
self.tree.setStyleSheet( "QTreeWidget::branch{border-image: url(none.png);}")
and then setting:
self.tree.setItemsExpandable(False)
You can successfully hide and disable the arrow decorators.
Personally, I have used this method when using a QPushButton to control the expanding of the QTreeWidgetItems.
example code
def __init__(self):
self.button = QtWidgets.QPushButton(text="toggle tree item")
self.button.setCheckable = True
self.button.toggled.connect(self.button_clicked)
def button_clicked(self, toggled):
self.tree_widget_item.setExpanded(toggled)