Не отображается имя в QTreeWidget
Пытаюсь написать структуру для проекта, вроде всё работает, но вот имя не отображается в дереве.
Хотя настройки видят правильное имя проекта. Пробовал всё, что только можно, не понимаю абсолютно, почему так происходит
tree.py
import sys
from PyQt5.QtWidgets import (
QApplication, QTreeWidget, QTreeWidgetItem, QWidget,
QVBoxLayout, QMainWindow, QMenu, QAction, QInputDialog,
QMessageBox, QPushButton, QHBoxLayout, QLabel, QLineEdit,
QFormLayout, QSpinBox, QDoubleSpinBox, QDateEdit, QGroupBox,
QDialog, QComboBox
)
from PyQt5.QtCore import Qt, QDate
try:
from _project import Project, createProject
except ImportError as e:
print("Ошибка: не удалось импортировать _project.py")
print(e)
sys.exit(1)
# === Главное окно ===
class TreeWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Система управления проектами")
self.resize(700, 600)
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
self.create_project_btn = QPushButton("➕ Создать проект")
self.create_project_btn.clicked.connect(self.create_new_project)
layout.addWidget(self.create_project_btn)
# === QTreeWidget ===
self.tree = QTreeWidget()
self.tree.setHeaderLabels(["Элемент"])
self.tree.itemClicked.connect(self.on_item_clicked)
self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
self.tree.customContextMenuRequested.connect(self.open_context_menu)
layout.addWidget(self.tree)
# Скрыть дерево, если нет проектов
#self.tree.hide()
# Храним текущий активный проект
#self.current_project = None
def create_new_project(self):
dialog = ProjectCreationDialog(self)
if dialog.exec_() == QDialog.Accepted:
data = dialog.get_data()
# Создаём проект
project = createProject(
name=data["name"],
autor=data["author"],
start_date=data["start_date"]
)
# Обновляем атрибуты проекта
project._ambientMinTemp5Day = data["ambientMinTemp5Day"]
project._ambientMinTemp = data["ambientMinTemp"]
project._ambientMaxTemp = data["ambientMaxTemp"]
project._startUpMinTemp = data["startUpMinTemp"]
project._fiveDaysDiameter = data["fiveDaysDiameter"]
project._exZone = data["exZone"]
project._nomVoltagePN = data["nomVoltagePN"]
project._nomVoltagePP = data["nomVoltagePP"]
project._nomVoltageUp = data["nomVoltageUp"]
project._nomVoltageDown = data["nomVoltageDown"]
project._circuitBreakerMaxCurrent = data["circuitBreakerMaxCurrent"]
# Добавляем проект в дерево
self.add_project_to_tree(project)
# Показываем дерево (если было скрыто)
self.tree.show()
def add_project_to_tree(self, project):
"""Добавляет проект в QTreeWidget"""
item = QTreeWidgetItem([project._projectName])
item.setData(0, 1, project)
item.setData(0, 2, "project")
self.tree.addTopLevelItem(item)
def on_item_clicked(self, item, column):
obj = item.data(0, 1)
if obj:
print(f"Выбран: {item.text(0)}")
def open_context_menu(self, position):
"""Контекстное меню"""
item = self.tree.itemAt(position)
if not item:
return
node_type = item.data(0, 2)
menu = QMenu(self)
if node_type == "project":
project = item.data(0, 1)
# Переименовать
rename_action = QAction("🔁 Переименовать проект", self)
rename_action.triggered.connect(lambda: self.rename_project(item, project))
# Сохранить
save_action = QAction("💾 Сохранить проект", self)
save_action.triggered.connect(lambda: self.save_project(project))
# Загрузить (пример, можно расширить)
load_action = QAction("📥 Загрузить проект", self)
load_action.triggered.connect(lambda: self.load_project())
# Настройки
settings_action = QAction("⚙️ Настройки проекта", self)
settings_action.triggered.connect(lambda: self.edit_project_settings(item, project))
# Создать трубопровод
new_pipe_action = QAction("➕ Создать трубопровод", self)
new_pipe_action.triggered.connect(lambda: self.create_pipe_line(item, project))
menu.addAction(rename_action)
menu.addAction(save_action)
menu.addAction(load_action)
menu.addAction(settings_action)
menu.addSeparator()
menu.addAction(new_pipe_action)
elif node_type == "pipe":
pipe = item.data(0, 1)
rename_action = QAction("🔁 Переименовать трубу", self)
rename_action.triggered.connect(lambda: self.rename_pipe(item, pipe))
calc_action = QAction("🧮 Рассчитать EHT", self)
calc_action.triggered.connect(pipe.calculateEHT)
menu.addAction(rename_action)
menu.addAction(calc_action)
elif node_type == "segment":
edit_action = QAction("📝 Редактировать сегмент", self)
edit_action.triggered.connect(lambda: self.edit_segment(item))
menu.addAction(edit_action)
menu.exec_(self.tree.viewport().mapToGlobal(position))
def rename_project(self, item, project):
text, ok = QInputDialog.getText(
self, "Переименовать проект", "Новое имя:",
text=project._projectName
)
if ok and text.strip():
new_name = text.strip()
project._projectName = new_name # сначала обновляем объект
item.setText(0, new_name) # потом отражаем в дереве
item.setData(0, 1, project)
item.setData(0, 2, "project")
def rename_pipe(self, item, pipe):
text, ok = QInputDialog.getText(
self, "Переименовать трубу", "Новое имя:",
text=pipe._pipeLineName
)
if ok and text.strip():
pipe._pipeLineName = text.strip()
item.setText(0, text.strip())
def edit_segment(self, item):
segment = item.data(0, 1)
msg = (
f"🔢 Номер сегмента: {segment._segmentNumber}\n"
f"📏 Длина: {segment._segmentLength} м\n"
f"🌡 Температура поддержания: {segment._tempMaintain} °C\n"
f"🏗 Тип изоляции: {segment._insulationType}\n"
f"📍 Координаты подключения: {segment._connCoordinates}"
)
QMessageBox.information(self, "Свойства сегмента", msg)
def save_project(self, project):
filename, ok = QInputDialog.getText(
self, "Сохранить проект", "Имя файла (без расширения):",
text=f"{project._projectName}.json"
)
if ok and filename.strip():
if not filename.endswith(".json"):
filename += ".json"
try:
project.saveProject(filename)
QMessageBox.information(self, "Сохранение", f"Проект сохранён как:\n{filename}")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Не удалось сохранить проект:\n{str(e)}")
def load_project(self):
filename, ok = QInputDialog.getText(
self, "Загрузить проект", "Имя файла (с расширением .json):"
)
if ok and filename.strip():
try:
project = Project.loadProject(filename.strip())
self.add_project_to_tree(project)
self.tree.show()
self.create_project_btn.hide()
QMessageBox.information(self, "Загрузка", f"Проект '{project._projectName}' загружен.")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить проект:\n{str(e)}")
def edit_project_settings(self, item, project):
dialog = ProjectSettingsDialog(project, self)
if dialog.exec_() == QDialog.Accepted:
props = dialog.get_properties()
project.setProjectProperties(props) # обновляем объект
# Обновляем видимые данные в дереве
new_name = project._projectName
item.setText(0, new_name) # изменяем отображаемый текст
item.setData(0, 1, project) # обновляем хранимый объект
item.setData(0, 2, "project") # сохраняем тип узла
print(f"Проект переименован в: {new_name}") # отладка
def create_pipe_line(self, project_item, project):
name, ok = QInputDialog.getText(self, "Новый трубопровод", "Имя трубопровода:")
if ok and name.strip():
pipe = project.createPipeLine(name.strip())
pipe_item = QTreeWidgetItem([pipe._pipeLineName])
pipe_item.setData(0, 1, pipe)
pipe_item.setData(0, 2, "pipe")
project_item.addChild(pipe_item)
# === Диалог создания проекта ===
class ProjectCreationDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Создать новый проект")
self.resize(400, 500)
layout = QVBoxLayout(self)
# Форма
form = QFormLayout()
self.name_edit = QLineEdit()
self.author_edit = QLineEdit()
self.date_edit = QDateEdit()
self.date_edit.setDisplayFormat("dd.MM.yyyy")
self.date_edit.setDate(QDate.currentDate())
self.date_edit.setCalendarPopup(True)
form.addRow("Название проекта:", self.name_edit)
form.addRow("Автор:", self.author_edit)
form.addRow("Дата начала:", self.date_edit)
layout.addLayout(form)
# Группа климатических условий
climate_group = QGroupBox("Климатические условия")
climate_layout = QFormLayout()
self.amb_min_5day = QDoubleSpinBox()
self.amb_min_5day.setRange(-100, 100)
self.amb_min_5day.setValue(-35)
climate_layout.addRow("Мин. t₅ дней (°C):", self.amb_min_5day)
self.amb_min = QDoubleSpinBox()
self.amb_min.setRange(-100, 100)
self.amb_min.setValue(-40)
climate_layout.addRow("Мин. t (°C):", self.amb_min)
self.amb_max = QDoubleSpinBox()
self.amb_max.setRange(-100, 100)
self.amb_max.setValue(40)
climate_layout.addRow("Макс. t (°C):", self.amb_max)
self.startup_min = QDoubleSpinBox()
self.startup_min.setRange(-100, 100)
self.startup_min.setValue(-20)
climate_layout.addRow("Мин. t пуска (°C):", self.startup_min)
self.diameter_5days = QDoubleSpinBox()
self.diameter_5days.setRange(0, 1000)
self.diameter_5days.setValue(150)
climate_layout.addRow("Диаметр 150 мм:", self.diameter_5days)
climate_group.setLayout(climate_layout)
layout.addWidget(climate_group)
# Взрывоопасная зона — через QInputDialog.getItem
self.ex_zone_combo = QComboBox()
self.ex_zone_combo.addItems(["Нет", "Да"])
layout.addWidget(QLabel("Взрывоопасная зона:"))
layout.addWidget(self.ex_zone_combo)
voltage_group = QGroupBox("Параметры сети")
voltage_layout = QFormLayout()
self.voltage_pn = QDoubleSpinBox()
self.voltage_pn.setRange(0, 1000)
self.voltage_pn.setValue(230)
voltage_layout.addRow("Uₚₙ (В):", self.voltage_pn)
self.voltage_pp = QDoubleSpinBox()
self.voltage_pp.setRange(0, 1000)
self.voltage_pp.setValue(400)
voltage_layout.addRow("Uₚₚ (В):", self.voltage_pp)
self.voltage_up = QDoubleSpinBox()
self.voltage_up.setRange(0, 1000)
self.voltage_up.setValue(690)
voltage_layout.addRow("Uₚₐₙₑₗ (В):", self.voltage_up)
self.voltage_down = QDoubleSpinBox()
self.voltage_down.setRange(0, 1000)
self.voltage_down.setValue(24)
voltage_layout.addRow("Uₙₐₚᵣₐₕ (В):", self.voltage_down)
self.cb_current = QDoubleSpinBox()
self.cb_current.setRange(0, 10000)
self.cb_current.setValue(0)
voltage_layout.addRow("Макс. ток АВ (А):", self.cb_current)
voltage_group.setLayout(voltage_layout)
layout.addWidget(voltage_group)
buttons = QHBoxLayout()
ok_btn = QPushButton("Создать")
ok_btn.clicked.connect(self.accept)
cancel_btn = QPushButton("Отмена")
cancel_btn.clicked.connect(self.reject)
buttons.addStretch()
buttons.addWidget(ok_btn)
buttons.addWidget(cancel_btn)
layout.addLayout(buttons)
def get_data(self):
return {
"name": self.name_edit.text().strip() or "Безымянный проект",
"author": self.author_edit.text().strip() or "Неизвестный автор",
"start_date": self.date_edit.date().toPyDate(),
"ambientMinTemp5Day": self.amb_min_5day.value(),
"ambientMinTemp": self.amb_min.value(),
"ambientMaxTemp": self.amb_max.value(),
"startUpMinTemp": self.startup_min.value(),
"fiveDaysDiameter": self.diameter_5days.value(),
"exZone": self.ex_zone_combo.currentText() == "Да",
"nomVoltagePN": self.voltage_pn.value(),
"nomVoltagePP": self.voltage_pp.value(),
"nomVoltageUp": self.voltage_up.value(),
"nomVoltageDown": self.voltage_down.value(),
"circuitBreakerMaxCurrent": self.cb_current.value() or None,
}
class ProjectSettingsDialog(QDialog):
def __init__(self, project, parent=None):
super().__init__(parent)
self.setWindowTitle(f"Настройки проекта: {project._projectName}")
self.project = project
layout = QVBoxLayout(self)
form = QFormLayout()
self.name_edit = QLineEdit(project._projectName or "")
self.author_edit = QLineEdit(project._projectAutor or "")
self.amb_min_5day = QDoubleSpinBox()
self.amb_min_5day.setRange(-100, 100)
self.amb_min_5day.setValue(project._ambientMinTemp5Day)
self.amb_min = QDoubleSpinBox()
self.amb_min.setRange(-100, 100)
self.amb_min.setValue(project._ambientMinTemp)
self.amb_max = QDoubleSpinBox()
self.amb_max.setRange(-100, 100)
self.amb_max.setValue(project._ambientMaxTemp)
self.startup_min = QDoubleSpinBox()
self.startup_min.setRange(-100, 100)
self.startup_min.setValue(project._startUpMinTemp)
self.diameter_5days = QDoubleSpinBox()
self.diameter_5days.setRange(0, 1000)
self.diameter_5days.setValue(project._fiveDaysDiameter)
self.voltage_pn = QDoubleSpinBox()
self.voltage_pn.setRange(0, 1000)
self.voltage_pn.setValue(project._nomVoltagePN)
self.voltage_pp = QDoubleSpinBox()
self.voltage_pp.setRange(0, 1000)
self.voltage_pp.setValue(project._nomVoltagePP)
self.voltage_up = QDoubleSpinBox()
self.voltage_up.setRange(0, 1000)
self.voltage_up.setValue(project._nomVoltageUp)
self.voltage_down = QDoubleSpinBox()
self.voltage_down.setRange(0, 1000)
self.voltage_down.setValue(project._nomVoltageDown)
self.cb_current = QDoubleSpinBox()
self.cb_current.setRange(0, 10000)
self.cb_current.setValue(project._circuitBreakerMaxCurrent or 0)
form.addRow("Название:", self.name_edit)
form.addRow("Автор:", self.author_edit)
form.addRow("Мин. t₅ дней (°C):", self.amb_min_5day)
form.addRow("Мин. t (°C):", self.amb_min)
form.addRow("Макс. t (°C):", self.amb_max)
form.addRow("Мин. t пуска (°C):", self.startup_min)
form.addRow("Диаметр 150 мм:", self.diameter_5days)
# Выпадающий список для взрывоопасной зоны
self.ex_combo = QComboBox()
self.ex_combo.addItems(["Нет", "Да"])
self.ex_combo.setCurrentText("Да" if project._exZone else "Нет")
form.addRow("Взрывоопасная зона:", self.ex_combo)
form.addRow("Uₚₙ (В):", self.voltage_pn)
form.addRow("Uₚₚ (В):", self.voltage_pp)
form.addRow("Uₚₐₙₑₗ (В):", self.voltage_up)
form.addRow("Uₙₐₚᵣₐₕ (В):", self.voltage_down)
form.addRow("Макс. ток АВ (А):", self.cb_current)
layout.addLayout(form)
buttons = QHBoxLayout()
ok_btn = QPushButton("Сохранить")
ok_btn.clicked.connect(self.accept)
cancel_btn = QPushButton("Отмена")
cancel_btn.clicked.connect(self.reject)
buttons.addStretch()
buttons.addWidget(ok_btn)
buttons.addWidget(cancel_btn)
layout.addLayout(buttons)
def get_properties(self):
return {
"projectName": self.name_edit.text().strip(),
"projectAutor": self.author_edit.text().strip(),
"ambientMinTemp5Day": self.amb_min_5day.value(),
"ambientMinTemp": self.amb_min.value(),
"ambientMaxTemp": self.amb_max.value(),
"startUpMinTemp": self.startup_min.value(),
"fiveDaysDiameter": self.diameter_5days.value(),
"exZone": self.ex_combo.currentText() == "Да",
"nomVoltagePN": self.voltage_pn.value(),
"nomVoltagePP": self.voltage_pp.value(),
"nomVoltageUp": self.voltage_up.value(),
"nomVoltageDown": self.voltage_down.value(),
"circuitBreakerMaxCurrent": self.cb_current.value() or None,
}
# === Запуск приложения ===
if __name__ == '__main__':
app = QApplication(sys.argv)
window = TreeWindow()
window.show()
sys.exit(app.exec_())
project.py
# КЛАСС ПРОЕКТ
import uuid
from datetime import date, datetime
from _pipeLine import PipeLine
import json
def serialize_obj(obj):
"""
Позволяет json.dumps() корректно обрабатывать:
- uuid.UUID → str
- date, datetime → ISO-строка
"""
if isinstance(obj, uuid.UUID):
return str(obj)
elif isinstance(obj, (date, datetime)):
return obj.isoformat()
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
def createProject(name=None, autor=None, start_date=None):
return Project(projectName=name, projectAutor=autor, projectStartDate=start_date)
class Project:
def __init__(self, projectName=None, projectAutor=None, projectStartDate=date.today()):
# Имя проекта
self._projectName = projectName
self._projectAutor = projectAutor
self._projectStartDate = projectStartDate
self._projectCurrentRevision = 1
self._projectID = uuid.uuid4()
self._ambientMinTemp5Day = -35
self._ambientMinTemp = -40
self._ambientMaxTemp = 40
self._startUpMinTemp = -20
self._fiveDaysDiameter = 150
# Атрибуты взрывоопасной зоны
self._exZone = True
self._nomVoltagePN = 230
self._nomVoltagePP = 400
self._nomVoltageUp = 690
self._nomVoltageDown = 24
self._circuitBreakerMaxCurrent = None
# Объекты проекта
self._pipeLines = []
self._pipeSegments = []
self._deletedPipeLines = []
self._deletedPipeSegments = []
self._pipeLinesCounter = 0
def getProjectProperties(self):
ProjectProperties = {"projectName":self._projectName,
"projectAutor":self._projectAutor,
"ambientMinTemp5Day":self._ambientMinTemp5Day,
"ambientMinTemp":self._ambientMinTemp,
"ambientMaxTemp":self._ambientMaxTemp,
"startUpMinTemp":self._startUpMinTemp,
"fiveDaysDiameter":self._fiveDaysDiameter,
"exZone":self._exZone,
"nomVoltagePN":self._nomVoltagePN,
"nomVoltagePP":self._nomVoltagePP,
"nomVoltageUp":self._nomVoltageUp,
"nomVoltageDown":self._nomVoltageDown,
"circuitBreakerMaxCurrent":self._circuitBreakerMaxCurrent,
}
return ProjectProperties
def setProjectProperties(self, ProjectProperties):
self._projectName = ProjectProperties["projectName"]
self._projectAutor = ProjectProperties["projectAutor"]
self._ambientMinTemp5Day = ProjectProperties["ambientMinTemp5Day"]
self._ambientMinTemp = ProjectProperties["ambientMinTemp"]
self._ambientMaxTemp = ProjectProperties["ambientMaxTemp"]
self._startUpMinTemp = ProjectProperties["startUpMinTemp"]
self._fiveDaysDiameter = ProjectProperties["fiveDaysDiameter"]
self._exZone = ProjectProperties["exZone"]
self._nomVoltagePN = ProjectProperties["nomVoltagePN"]
self._nomVoltagePP = ProjectProperties["nomVoltagePP"]
self._nomVoltageUp = ProjectProperties["nomVoltageUp"]
self._nomVoltageDown = ProjectProperties["nomVoltageDown"]
self._circuitBreakerMaxCurrent = ProjectProperties["circuitBreakerMaxCurrent"]
return
# Создание объектов
def createPipeLine(self, pipeLineName):
self._pipeLinesCounter += 1
newPipeLine = PipeLine(pipeLineName, self)
newPipeLine._pipeLineNumber = self._pipeLinesCounter
self._pipeLines.append(newPipeLine)
return newPipeLine
def deletePipeLine(self, pipeLine):
if pipeLine in self._pipeLines:
self._pipeLines.remove(pipeLine)
self._deletedPipeLines.append(pipeLine)
def createPipeSegment(self):
pass
# Другие типы обогреваемых объектов будут добавлены позднее
# def createVessel(self):
# pass
# def createInstrument(self):
# pass
# def createRoof(self):
# pass
# def createFloor(self):
# pass
# Если происходили изменения в настройках проекта, нужно обновить их для всех обогреваемых объектов
def updateJoinedObjects(self):
for pipeLine in self._pipeLines:
pipeLine.sync_with_project()
# СОХРАНЕНИЕ И ЗАГРУЗКА
def to_dict(self):
return {
"projectID": self._projectID,
"projectName": self._projectName,
"projectAutor": self._projectAutor,
"projectStartDate": self._projectStartDate,
"projectCurrentRevision": self._projectCurrentRevision,
"projectProperties": self.getProjectProperties(),
"pipeLines": [pl.to_dict() for pl in self._pipeLines]
}
@staticmethod
def from_dict(data, insulationLib=None):
from _InsulationLib import defaultInsulationLib
if insulationLib is None:
insulationLib = defaultInsulationLib
start_date = date.fromisoformat(data["projectStartDate"]) if data["projectStartDate"] else date.today()
project = Project(
projectName=data["projectName"],
projectAutor=data["projectAutor"],
projectStartDate=start_date
)
project._projectID = uuid.UUID(data["projectID"])
project._projectCurrentRevision = data["projectCurrentRevision"]
project.setProjectProperties(data["projectProperties"])
for pl_data in data["pipeLines"]:
pipeLine = PipeLine.from_dict(pl_data, project, insulationLib)
project._pipeLines.append(pipeLine)
project._pipeLinesCounter = max(project._pipeLinesCounter, pipeLine._pipeLineNumber)
return project
def saveProject(self, filename: str) -> None:
"""
Сохраняет проект в JSON-файл с использованием to_dict() и пользовательского сериализатора.
:param filename: Имя файла (например, 'my_project.json')
"""
try:
with open(filename, 'w', encoding='utf-8') as f:
json.dump(
self.to_dict(), # преобразуем весь проект в словарь
f,
default=serialize_obj, # обрабатываем UUID, date и т.д.
ensure_ascii=False,
indent=4
)
print(f"✅ Проект успешно сохранён: {filename}")
except Exception as e:
print(f"❌ Ошибка при сохранении проекта: {e}")
raise
@staticmethod
def loadProject(filename: str, insulationLib=None):
"""
Загружает проект из JSON-файла.
:param filename: Путь к файлу
:param insulationLib: Библиотека изоляции (по умолчанию — defaultInsulationLib)
:return: Новый объект Project
"""
# Подгружаем библиотеку изоляции, если не передана
if insulationLib is None:
from _InsulationLib import defaultInsulationLib
insulationLib = defaultInsulationLib
try:
with open(filename, 'r', encoding='utf-8') as f:
data = json.load(f) # Загружаем словарь из JSON
# Восстанавливаем проект через from_dict
project = Project.from_dict(data, insulationLib=insulationLib)
print(f"✅ Проект успешно загружен: {filename}")
return project
except FileNotFoundError:
print(f"❌ Файл не найден: {filename}")
raise
except KeyError as e:
print(f"❌ Ошибка структуры файла (отсутствует ключ): {e}")
raise
except Exception as e:
print(f"❌ Ошибка при загрузке проекта: {e}")
raise
Источник: Stack Overflow на русском