Created
April 2, 2025 10:32
-
-
Save tsgiannis/58b0e73c6f9878095bf246b0756e9b89 to your computer and use it in GitHub Desktop.
ActiveX alike QT Treeview
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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