PYQT5 - UI Function working for Layout 1 not for Layout 2
-
we have an application Yaml editor in which on click on MDI sub window, the Yaml editor sub module gets opened and submodule has multiple tabs and those multiple tabs will have sub tabs. on top of the submodule there is a file menu which has open , save , exit and actions menu which has addrow, remove row etc. addrow should add row in the loaded yaml content inside the tab.
Direct Question:
File_1= yamleditor.py
File_2= treemodel.py
The problem we face is addrow(), remove row() function works fine when it is inside the Qtreeview model (file_2),but when it is called from a actions menu(file_1) the function gets executed but it is not reflecting in the ui?. The same error occurs in all the action item.
CODE REF for File_2: https://doc.qt.io/qtforpython/examples/example_widgets_itemviews_jsonmodel.html#json-model-example
yaml_editor.py
import sys import traceback from PyQt5.QtWidgets import QApplication, QWidget, QTreeView, QMessageBox, QVBoxLayout, QLabel, QLineEdit, QComboBox, QListWidget, QHBoxLayout, QPushButton, QFileDialog, QProgressBar, QListView, QAbstractItemView, QTabWidget, QMdiArea, QMainWindow, QMenu, QToolBar, QAction, QFrame from PyQt5.QtGui import QIntValidator, QFont from PyQt5.QtCore import Qt import yaml import os import json from datetime import datetime import subprocess from threading import * from tree_model import TreeModelClass as ETreeWindw class AnaYAMLEditor(QWidget): def __init__(self): super().__init__() self.title = 'YAML Editor' self.ui_fields_access = 0 self.config = yaml.safe_load(open("input.yaml")) self.config_key = 'yaml_editor' self.current_file_path = os.getcwd() self.module_name = "Tab_1" self.save_flag = 0 try: self.initUI() self.showMaximized() self.show() except Exception: print(traceback.format_exc()) def initUI(self): print(f"{self.title} app started at {datetime.now()}") self.setWindowTitle(self.title) self.tabs = QTabWidget() self.analysis_config_tab = QWidget() self.tabs.addTab(self.analysis_config_tab, "Tab_1") self.tabs.setCurrentIndex(0) self.tabs.currentChanged.connect(self.tab_changed) self.toolbar = QToolBar(self) self.file_meu = QPushButton(" File") self.actions_menu = QPushButton("Actions") self.file_meu.setStyleSheet(''' QPushButton{ border: none; } QPushButton:menu-indicator{ image: none; } QPushButton:hover { background-color: lightgrey; } QPushButton:pressed { background-color: lightgrey; } QPushButton:align{ text-align: center; } ''') self.file_meu.setFont(QFont("Arial", 10)) self.actions_menu.setStyleSheet(''' QPushButton{ border: none; } QPushButton:menu-indicator{ image: none; } QPushButton:hover { background-color: lightgrey; } ''') self.actions_menu.setFont(QFont("Arial", 10)) self.toolbar.addWidget(self.file_meu) self.toolbar.addWidget(self.actions_menu) menu_file = QMenu() menu_action = QMenu() self.actions_menu.setMenu(menu_action) self.file_meu.setMenu(menu_file) self.file_menu_1 = QAction("Open", self) self.file_menu_1.triggered.connect(lambda: self.menu_open(self.module_name)) self.file_menu_1.setShortcut("Ctrl+O") self.file_menu_1.setStatusTip("Open a file") menu_file.addAction(self.file_menu_1) self.file_menu_2 = QAction("Open Containing Folder", self) menu_file_2 = QMenu() menu_file_2.addAction("File Explorer") menu_file_2.addAction("cmd") menu_file_2.actions()[0].triggered.connect(lambda: self.menu_open_containing_folder(self.module_name)) menu_file_2.actions()[1].triggered.connect(self.invoke_menu_cmd_thread) self.file_menu_2.setMenu(menu_file_2) self.file_menu_2.setStatusTip("Open Containing Folder") self.file_menu_2.triggered.connect(lambda: self.menu_open_containing_folder(self.module_name)) menu_file.addAction(self.file_menu_2) self.file_menu_3 = QAction("Save", self) self.file_menu_3.triggered.connect(lambda: self.menu_save(self.module_name)) menu_file.addAction(self.file_menu_3) self.file_menu_4 = QAction("Save As", self) self.file_menu_4.triggered.connect(lambda: self.menu_save_as(self.module_name)) menu_file.addAction(self.file_menu_4) self.file_menu_4 = QAction("Exit", self) self.file_menu_4.triggered.connect(self.closeEvent) menu_file.addAction(self.file_menu_4) self.actions_menu_1 = QAction("Add Row", self) self.actions_menu_1.triggered.connect(lambda: self.menu_add_row(self.module_name)) menu_action.addAction(self.actions_menu_1) self.actions_menu_2 = QAction("Remove Row", self) self.actions_menu_2.triggered.connect(lambda: self.menu_remove_row(self.module_name)) menu_action.addAction(self.actions_menu_2) self.actions_menu_3 = QAction("Insert Child Row", self) self.actions_menu_3.triggered.connect(lambda: self.menu_add_child_row(self.module_name)) menu_action.addAction(self.actions_menu_3) self.vertical_layout = QVBoxLayout(self) self.vertical_layout.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.h_box_layout = QHBoxLayout(self) self.h_box_layout.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.h_box_layout.addWidget(self.toolbar) self.vertical_layout.addLayout(self.h_box_layout) self.horizontal_line = QFrame() self.horizontal_line.setFrameShape(QFrame.HLine) self.horizontal_line.setFrameShadow(QFrame.Sunken) self.vertical_layout.addWidget(self.horizontal_line) self.h_box_layout = QHBoxLayout() self.h_box_layout.addWidget(self.tabs) self.vertical_layout.addLayout(self.h_box_layout) self.vertical_layout.setContentsMargins(0, 0, 0, 0) self.setWindowTitle(self.title) self.show() def menu_open(self, module_name): file_path = QFileDialog.getOpenFileName(self, 'Open File', '', "All Files (*)") print(file_path) if file_path[0] != '': self.current_file_path = file_path[0] self.setWindowTitle(f"{self.title} - {file_path[0]}") self.edit_tree_window = ETreeWindw(self.module_name, self.current_file_path, self.save_flag) self.horizontal_box_layout = QHBoxLayout() self.horizontal_box_layout.addWidget(self.edit_tree_window) self.analysis_config_tab.setLayout(self.horizontal_box_layout) self.h_box_layout = QHBoxLayout() self.h_box_layout.addWidget(self.tabs) self.vertical_layout.addLayout(self.h_box_layout) self.show() def menu_open_containing_folder(self, module_name): FILEBROWSER_PATH = os.path.join(os.getenv('WINDIR'), 'explorer.exe') subprocess.run([FILEBROWSER_PATH, '/select,', os.path.normpath(self.current_file_path)]) return def invoke_menu_cmd_thread(self): self.t1 = Thread(target=self.menu_open_cmd) self.t1.start() def menu_open_cmd(self): os.system("start cmd /K ") os.system("pause") return def update_window_title_edited(self): temp_title = self.windowTitle() temp_title.replace(" - ", " - *") self.setWindowTitle(temp_title) return def menu_save(self, module_name): self.edit_tree_window.save_file() temp_title = self.windowTitle() temp_title.replace(" - *", " - ") self.setWindowTitle(temp_title) return def menu_add_row(self, tab_name="Tab_1"): e_tree_class = ETreeWindw(tab_name, self.current_file_path, self.save_flag) e_tree_class.add_row() def menu_add_child_row(self, tab_name="Tab_1"): e_tree_class = ETreeWindw(tab_name, self.current_file_path, self.save_flag) e_tree_class.add_child_row() def menu_remove_row(self, tab_name="Tab_1"): e_tree_class = ETreeWindw(tab_name, self.current_file_path, self.save_flag) e_tree_class.remove_row() def tab_changed(self, index): self.module_name = self.tabs.tabText(index) print(f"Tab changed to {self.tabs.tabText(index)}") if self.tabs.tabText(index) == 'Tab_1': self.edit_tree_window = ETreeWindw(self.tabs.tabText(index), self.current_file_path, self.save_flag) self.horizontal_box_layout = QHBoxLayout() self.horizontal_box_layout.addWidget(self.edit_tree_window) self.analysis_config_tab.setLayout(self.horizontal_box_layout) self.show() if __name__ == '__main__': app = QApplication(sys.argv) ex = AnaYAMLEditor() ex.show() sys.exit(app.exec_())
tree_model.py
from fileinput import filename import json import sys import traceback from typing import Any, List, Dict, Union import os from PySide6.QtWidgets import QTreeView, QApplication from PySide6.QtCore import QAbstractItemModel, QModelIndex, QObject, Qt from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * import yaml class TreeItem: def __init__(self, parent: "TreeItem" = None): self._parent = parent self._key = "" self._value = "" self._comment = "" self._key_type = None self._value_type = None self._comment_type = None self._children = [] self.config = yaml.safe_load(open("input.yaml")) self.value_splitter = "###" def appendChild(self, item: "TreeItem"): self._children.append(item) def child(self, row: int) -> "TreeItem": return self._children[row] def parent(self) -> "TreeItem": return self._parent def childCount(self) -> int: return len(self._children) def row(self) -> int: return self._parent._children.index(self) if self._parent else 0 @property def key(self) -> str: return self._key @key.setter def key(self, key: str): self._key = key @property def key_type(self): return self._key_type @key_type.setter def key_type(self, key): self._key_type = key @property def value(self) -> str: return self._value @value.setter def value(self, value: str): self._value = value @property def value_type(self): return self._value_type @value_type.setter def value_type(self, value): self._value_type = value @property def comment(self) -> str: return self._comment @comment.setter def comment(self, comment: str): self._comment = comment @property def comment_type(self): return self._comment_type @comment_type.setter def comment_type(self, comment): self._comment_type = comment @classmethod def load( cls, value: Union[List, Dict], parent: "TreeItem" = None, sort=False ) -> "TreeItem": try: rootItem = TreeItem(parent) rootItem.key = "root" if isinstance(value, dict): items = sorted(value.items()) if sort else value.items() for key, value in items: child = cls.load(value, rootItem) child.key = key child.value_type = type(value) rootItem.appendChild(child) elif isinstance(value, list): for index, value in enumerate(value): child = cls.load(value, rootItem) child.key = index child.value_type = type(value) rootItem.appendChild(child) else: if isinstance(value, int): value = str(value) rootItem.value = str(value) rootItem.value_type = type(value) rootItem.comment = "" rootItem.comment_type = type(value) elif isinstance(value, str): if not "###" in value: rootItem.value = str(value) rootItem.value_type = type(value) rootItem.comment = "" rootItem.comment_type = type(value) else: rootItem.value = value.split("###")[0] rootItem.value_type = type(value) rootItem.comment = value.split("###")[-1] rootItem.comment_type = type(value) elif isinstance(value, float): rootItem.value = str(value) rootItem.value_type = type(value) rootItem.comment = "" rootItem.comment_type = type(value) except Exception as e: pass finally: return rootItem class JsonModel(QAbstractItemModel): def __init__(self, module_name, yaml_file_name,save_flag, parent: QObject = None): super().__init__(parent) self.config = yaml.safe_load(open("input.yaml")) self.value_splitter = "###" self._rootItem = TreeItem() self._headers = ("key", "value", "Comments") self._module_name = module_name self.yaml_file_name = yaml_file_name self.save_flag = save_flag def clear(self): self.load({}) def load(self, document: dict): try: assert isinstance( document, (dict, list, tuple) ), "`document` must be of dict, list or tuple, " f"not {type(document)}" self.beginResetModel() self._rootItem = TreeItem.load(document) self._rootItem.value_type = type(document) self.endResetModel() except Exception: pass return False finally: return True def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> Any: if not index.isValid(): return None item = index.internalPointer() if role == Qt.DisplayRole: if index.column() == 0: return item.key if index.column() == 1: return item.value if index.column() == 2: return item.comment elif role == Qt.EditRole: if index.column() == 1: if self.value_splitter in item.value: QMessageBox.about(self, "Information", f"### is not allowed in value") return item.value elif index.column() == 0: return item.key elif index.column() == 2: return item.comment def setData(self, index: QModelIndex, value: Any, role: Qt.ItemDataRole): if role == Qt.EditRole: if index.column() == 1: item = index.internalPointer() item.value = str(value) self.dataChanged.emit(index, index, [Qt.EditRole]) # RK HERE we can write logics to file save ### self.save_data(self._rootItem) return True elif index.column() == 0: item = index.internalPointer() item.key = str(value) self.dataChanged.emit(index, index, [Qt.EditRole]) self.save_data(self._rootItem) return True elif index.column() == 2: item = index.internalPointer() item.comment = str(value) self.dataChanged.emit(index, index, [Qt.EditRole]) self.save_data(self._rootItem) return True return False def read_json_file(self): with open(self.yaml_file_name) as file: data = json.load(file) return data def save_data(self, to_write_data): _space = " " to_write_dict = {} for f_data in to_write_data._children: if len(f_data._children) !=0: for _child in f_data._children: _child.comment = _child.comment if not _child.comment is None else "" to_write_dict[f_data.key] = {} _child_value = _child.value if _space in _child.value: _child_value = _child.value.replace(_space,'') _child_value = f"{_child.value}{_space}" to_write_dict[f_data.key][_child.key] = _child_value + self.value_splitter + _child.comment.strip() else: f_data.comment = f_data.comment if not f_data.comment is None else "" to_write_value = f_data.value if _space in f_data.value: to_write_value = f_data.value.replace(_space,'') to_write_value = f"{f_data.value}{_space}" to_write_dict[f_data.key] = to_write_value + self.value_splitter + f_data.comment.strip() module_dict = {} full_file_data = yaml.safe_load(open(self.yaml_file_name)) if "," in self._module_name: main_module = self._module_name.split(",")[0] sub_module = self._module_name.split(",")[-1] module_dict[main_module] = {} module_dict[main_module][sub_module] = to_write_dict else: module_dict[self._module_name] = to_write_dict update_file_content = {**full_file_data, **module_dict} TreeModelClass._tree_document = to_write_dict pass if self.save_flag ==1: with open(self.yaml_file_name , 'w') as f: yaml.safe_dump(update_file_content, f, indent=4, default_flow_style=False) def save_dict_data(self, to_write_dict, module_name, save_flag=0): module_dict = {} full_file_data = yaml.safe_load(open(self.yaml_file_name)) if "," in self.module_name: main_module = self.module_name.split(",")[0] sub_module = self.module_name.split(",")[-1] module_dict[main_module] = {} module_dict[main_module][sub_module] = to_write_dict else: module_dict[self.module_name] = to_write_dict update_file_content = {**full_file_data, **module_dict} TreeModelClass._tree_document = to_write_dict pass if self.save_flag ==1 or save_flag ==1: with open(self.yaml_file_name , 'w') as f: yaml.safe_dump(update_file_content, f, indent=4, default_flow_style=False) def headerData( self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole ): if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: return self._headers[section] def index(self, row: int, column: int, parent=QModelIndex()) -> QModelIndex: if not self.hasIndex(row, column, parent): return QModelIndex() if not parent.isValid(): parentItem = self._rootItem else: parentItem = parent.internalPointer() childItem = parentItem.child(row) if childItem: return self.createIndex(row, column, childItem) else: return QModelIndex() def parent(self, index: QModelIndex) -> QModelIndex: if not index.isValid(): return QModelIndex() childItem = index.internalPointer() parentItem = childItem.parent() if parentItem == self._rootItem: return QModelIndex() return self.createIndex(parentItem.row(), 0, parentItem) def rowCount(self, parent=QModelIndex()): if parent.column() > 0: return 0 if not parent.isValid(): parentItem = self._rootItem else: parentItem = parent.internalPointer() return parentItem.childCount() def columnCount(self, parent=QModelIndex()): return len(self._headers) def flags(self, index: QModelIndex) -> Qt.ItemFlags: flags = super(JsonModel, self).flags(index) return Qt.ItemIsEditable | flags def to_json(self, item=None): if item is None: item = self._rootItem nchild = item.childCount() if item.value_type is dict: document = {} for i in range(nchild): ch = item.child(i) document[ch.key] = self.to_json(ch) return document elif item.value_type == list: document = [] for i in range(nchild): ch = item.child(i) document.append(self.to_json(ch)) return document else: return item.value class TreeModelClass(QWidget): def __init__(self, module_name, file_name, save_flag): super(TreeModelClass, self).__init__() TreeModelClass._tree_document = {} self.config = yaml.safe_load(open("input.yaml")) self.yaml_file_name = file_name self.save_flag = save_flag # Toolbar menu item self.toolbar = QToolBar(self) self.add_row_action = QAction("Add Row", self) self.add_row_action.triggered.connect(self.add_row) self.toolbar.addAction(self.add_row_action) self.remove_row_action = QAction("Remove Row", self) self.remove_row_action.triggered.connect(self.remove_row) self.toolbar.addAction(self.remove_row_action) self.module_name = 'Heading_data' self.view = QTreeView() self.model = JsonModel(self.module_name, self.yaml_file_name, self.save_flag) self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.json_path = file_name if not os.path.exists(self.json_path): with open(self.json_path, 'w') as f: json.dump({}, f, indent=4) try: TreeModelClass._tree_document = yaml.safe_load(open(file_name)) except FileNotFoundError: QMessageBox.warning(self, "Warning", "Config file not found") return if "," in self.module_name: main_module = module_name.split(",")[0] sub_module = module_name.split(",")[-1] TreeModelClass._tree_document = TreeModelClass._tree_document[main_module][sub_module] self.model.load(TreeModelClass._tree_document) elif not "," in self.module_name and not self.module_name in TreeModelClass._tree_document: QMessageBox.about(self, "Information", f"{self.module_name} is not yet configured") return else: TreeModelClass._tree_document = TreeModelClass._tree_document[self.module_name] self.model.load(TreeModelClass._tree_document) QAbstractItemView.reset(self.view) self.view.setModel(self.model) self.view.resizeColumnToContents(0) self.view.resizeColumnToContents(1) self.view.expandAll() # create label "Heading" self.heading = QLabel("Heading") self.heading.setAlignment(Qt.AlignCenter) self.vertical_layout = QVBoxLayout(self) self.h_box_layout = QHBoxLayout() self.h_box_layout.addWidget(self.toolbar) self.vertical_layout.addLayout(self.h_box_layout) self.h_box_layout = QHBoxLayout() self.h_box_layout.addWidget(self.view) self.vertical_layout.addLayout(self.h_box_layout) def save_file(self): try: class__tree_document = TreeModelClass._tree_document JsonModel.save_dict_data(self, class__tree_document, self.module_name, 1) self.model.load(self._tree_document) try: self.view.setModel(self.model) except Exception: pass except Exception: pass def add_row(self): try: class__tree_document = TreeModelClass._tree_document pass pass temp_document = {"new_key": "value ### "} if "key" in class__tree_document: QMessageBox.about(self, "Information", f"This key already exists") self._tree_document = {**class__tree_document, **temp_document} class__tree_document = self._tree_document JsonModel.save_dict_data(self, class__tree_document, self.module_name) self.model.load(self._tree_document) try: self.view.setModel(self.model) except Exception: pass except Exception: pass def remove_row(self): pass if len(self.view.selectedIndexes()) == 0: QMessageBox.about(self, "Information", f"Please select a row to remove") return else: class__tree_document = TreeModelClass._tree_document if self.view.currentIndex().internalPointer()._value_type == dict: child_temp_dict_key = self.view.currentIndex().internalPointer().key del class__tree_document[child_temp_dict_key] self._tree_document = class__tree_document elif self.view.currentIndex().internalPointer()._value_type == str: child_temp_dict_key = self.view.currentIndex().internalPointer().key for _child_dict in class__tree_document: if isinstance(class__tree_document[_child_dict], dict): for _child_dict_key in class__tree_document[_child_dict]: if _child_dict_key == child_temp_dict_key: if len(class__tree_document[_child_dict]) == 1: _parent_key = self.view.selectedIndexes()[0].internalPointer()._parent.key del class__tree_document[_child_dict][_child_dict_key] break else: pass del class__tree_document[_child_dict][_child_dict_key] break elif isinstance(class__tree_document[_child_dict], str): if _child_dict == child_temp_dict_key: _parent_key = self.view.selectedIndexes()[0].internalPointer()._parent.key if _parent_key == "root": del class__tree_document[_child_dict] break else: class__tree_document[_parent_key] = class__tree_document[_child_dict] break self._tree_document = class__tree_document JsonModel.save_dict_data(self, class__tree_document, self.module_name) self.model.load(self._tree_document) self.view.setModel(self.model) if __name__ == "__main__": app = QApplication(sys.argv) window = TreeModelClass() app.exec()
input.yaml
Heading_data: data_2: '1.3.24 ###' data_3: 'asdf ###this is quote comment' data_4: '126 ###' working: 'fine ###'
-
@sudo_ranjith
Hello and welcome.So you are saying the code works OK for "file 2", whose code you post, but not for "file 1", whose code you do not post?
You really need to show non-working code. Actual code, if it's not identical to an example you quote, and preferably some minimal reproducible example where you have stripped out all the irrelevant stuff for others to wade through so that we only have to look at whatever is not working.
-
@sudo_ranjith
You have now pasted 700+ lines of code. Full of things including threads, OS commands, goodness knows what else. Sorry, but this is not for me, maybe someone else will choose to look through and figure. A minimal reproducible example means removing all extraneous code till you have the minimum which reproduces an issue, not just pasting the totality of what you have --- at which point you might even spot it yourself....A couple of observations:
class__tree_document = self._tree_document JsonModel.save_dict_data(self, class__tree_document, self.module_name) self.model.load(self._tree_document)
Verify whether the final
self._tree_document
contains the newly inserted data.except Exception: pass
You have this all over the place in your code. It means if, for whatever reason, there is an error you simply ignore it, do not report, and we/you would have no idea there has been a problem. This is dangerous at the best of times in Python, and certainly is inadvisable during development.
TreeModelClass._tree_document = {}
Perhaps not related to your issue, but why would you want to store the document to be used as a singleton class variable? I cannot imagine why you would not want it as a member variable.
-
@JonB
Please follow steps to reproduce the issue:-
download 3 code and save in a file
-
run the yaml_editor.py file you should see the UI like this
-
click on the file -> open -> choose "input.yaml"
-
click on Add row button - it will work (add new row)
- When you click on "Actions" -> "Add row" nothing will happen in the UI but backend functions will be invoked.
Note: "Point 5" is the exact problem that I'm facing.
Thanks in advance. -
-
self._tree_document contains the newly inserted data, but while loading the screen it was not populating the data.
I'm still investigating this issue.Note:
Actually, I have reduced a lot of code (I tried to reduce the number of code that you see), and I have added exceptions and logger in the code base, I thought it might not be relevant for the question so I removed many items.Anyway, thank you so much for your time.
I really appreciate your efforts,
Have a great day. -
@sudo_ranjith said in PYQT5 - UI Function working for Layout 1 not for Layout 2:
self._tree_document contains the newly inserted data
Does it? I pointed out you seem to be using
TreeModelClass._tree_document
....