Skip to content

Instantly share code, notes, and snippets.

@tsgiannis
Created April 2, 2025 10:32
Show Gist options
  • Save tsgiannis/58b0e73c6f9878095bf246b0756e9b89 to your computer and use it in GitHub Desktop.
Save tsgiannis/58b0e73c6f9878095bf246b0756e9b89 to your computer and use it in GitHub Desktop.
ActiveX alike QT Treeview
import sys
import random
from PyQt6.QtWidgets import (QApplication, QDialog, QVBoxLayout, QHBoxLayout,
QTreeWidget, QTreeWidgetItem, QPushButton,
QAbstractItemView, QStyleFactory, QMenu,
QHeaderView, QSizePolicy)
from PyQt6.QtCore import Qt, QSize, QPointF
from PyQt6.QtGui import QBrush, QColor, QPixmap, QPainter, QPen, QIcon, QRadialGradient, QAction
class TheUI(QDialog):
def __init__(self, args=None, parent=None):
super().__init__(parent)
self.setWindowTitle("Enhanced Tree Widget")
# Main layout with proper margins
self.layout1 = QVBoxLayout(self)
self.layout1.setContentsMargins(5, 5, 5, 5)
self.layout1.setSpacing(5)
# Create tree widget
self.treeWidget = QTreeWidget()
self.treeWidget.setSizePolicy(
QSizePolicy.Policy.Expanding,
QSizePolicy.Policy.Expanding
)
# Set style that shows lines
style = QStyleFactory.create('Windows' if 'Windows' in QStyleFactory.keys() else 'Fusion')
self.treeWidget.setStyle(style)
self.setStyle(style) # Apply to dialog as well for consistency
# Configure tree properties
self.treeWidget.setRootIsDecorated(True) # Show expand/collapse controls
self.treeWidget.setAlternatingRowColors(False)
self.treeWidget.setIndentation(20)
self.treeWidget.setAnimated(True)
# Enable drag and drop and context menu
self.treeWidget.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
self.treeWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.treeWidget.customContextMenuRequested.connect(self.showContextMenu)
# Buttons
self.button1 = QPushButton('Add')
self.button2 = QPushButton('Add Child')
# Button layout
self.layout2 = QHBoxLayout()
self.layout2.addWidget(self.button1)
self.layout2.addWidget(self.button2)
# Assemble main layout
self.layout1.addWidget(self.treeWidget)
self.layout1.addLayout(self.layout2)
# Configure tree headers with proper resizing
HEADERS = ("script", "chunksize", "mem")
self.treeWidget.setHeaderLabels(HEADERS)
self.treeWidget.setColumnCount(len(HEADERS))
# Set column resize modes
header = self.treeWidget.header()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Interactive) # First column - manually resizable
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Interactive) # Second column - manually resizable
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) # Third column - auto stretch
# Set initial widths
self.treeWidget.setColumnWidth(0, 200)
self.treeWidget.setColumnWidth(1, 100)
# Connect signals
self.button1.clicked.connect(self.addCmd)
self.button2.clicked.connect(self.addChildCmd)
# Initial population
self.resize(800, 600)
for i in range(6):
item = self.addCmd(i)
if i in (3, 4):
self.addChildCmd()
if i == 4:
self.addCmd(f'{i}-2', parent=item)
self.treeWidget.expandAll()
# Cleaner styling that preserves system icons
self.setStyleSheet("""
QTreeWidget {
background-color: #f5f5f5;
border: 1px solid #b0b0b0;
font-size: 14px;
}
QTreeWidget::item {
height: 28px;
padding: 4px;
}
QTreeWidget::item:selected {
background-color: #90CAF9;
color: black;
}
QHeaderView::section {
background-color: #d0d0d0;
padding: 6px;
border: none;
border-bottom: 2px solid #b0b0b0;
font-weight: bold;
}
""")
def showContextMenu(self, position):
item = self.treeWidget.itemAt(position)
menu = QMenu()
add_child_action = QAction("Add Child Node", self)
add_child_action.triggered.connect(lambda: self.addChildCmd())
menu.addAction(add_child_action)
if item:
delete_action = QAction("Delete Node", self)
delete_action.triggered.connect(lambda: self.deleteItem(item))
menu.addAction(delete_action)
menu.exec(self.treeWidget.viewport().mapToGlobal(position))
def addChildCmd(self):
parent = self.treeWidget.currentItem()
if not parent:
parent = self.treeWidget.invisibleRootItem()
self.addCmd(parent=parent)
self.treeWidget.setCurrentItem(parent)
def addCmd(self, i=None, parent=None):
"""Add an item to the tree widget"""
root = self.treeWidget.invisibleRootItem()
if parent is None:
parent = root
# Determine item text
if i is None:
if parent == root:
i = self.treeWidget.topLevelItemCount()
else:
parent_text = parent.text(0)
i = f"{parent_text[7:]}-{parent.childCount() + 1}"
# Create and configure the item
item = QTreeWidgetItem(parent, [f"script {i}", "1", "150"])
# Make items selectable and movable
item.setFlags(item.flags() |
Qt.ItemFlag.ItemIsSelectable |
Qt.ItemFlag.ItemIsEditable |
Qt.ItemFlag.ItemIsDragEnabled |
Qt.ItemFlag.ItemIsDropEnabled)
# Add icon only if it exists
node_icon = self.get_node_icon(item)
if node_icon:
item.setIcon(0, node_icon)
self.treeWidget.setCurrentItem(item)
self.treeWidget.expandItem(parent)
return item
def get_node_icon(self, item):
"""Simplified icon creation that won't interfere with system icons"""
try:
if item.parent() is None:
return self.create_colored_icon(QColor(Qt.GlobalColor.red))
elif random.random() < 0.2:
# Only return folder icon if the file exists
return QIcon("folder.png") if True else None # Replace True with os.path.exists()
else:
return self.create_colored_icon(QColor(Qt.GlobalColor.green))
except:
return None
def create_colored_icon(self, color):
"""
Creates a photorealistic-looking orb icon.
"""
size = QSize(18, 18)
pixmap = QPixmap(size)
pixmap.fill(Qt.GlobalColor.transparent)
painter = QPainter(pixmap)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
center = pixmap.rect().center()
center_f = QPointF(center)
radius = 7.5
gradient = QRadialGradient(center_f, radius)
gradient.setColorAt(0, color.lighter(120))
gradient.setColorAt(1, color.darker(120))
painter.setBrush(QBrush(gradient))
painter.setPen(QPen(Qt.PenStyle.NoPen))
painter.drawEllipse(center, 8, 8)
painter.end()
return QIcon(pixmap)
def deleteItem(self, item):
"""Remove the specified item from the tree"""
(item.parent() or self.treeWidget.invisibleRootItem()).removeChild(item)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
)
gui = TheUI()
gui.show()
sys.exit(app.exec())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment