I need to display the hierarchical model. Child nodes must be created when user expands the node. The number of child nodes is not known in advance. Some child nodes can be created immediately after expanding parent node. And some child nodes need time to get data by sending request and only then can be created.
So I create QTreeView + QSortFilterProxyModel + Qt model (QAbstractItemModel inheritor) + data model. The code below works fine with the proxy model. But without proxy model + for nodes that created immediately I have Process finished with exit code -1073741819 (0xC0000005) while expanding any node. I'm afraid that this error will also appear in the presence of a proxy model sooner or later.
UPD 1: I added the _populate_request signal with QueuedConnection to 'separate' fetchMore call stack from adding/removing nodes from model (thanks to #musicamante - it's the same idea as a single shot timer). That helped. But this step is not obvious for me and I still open for ideas why direct calls leads to crash.
UPD 2: Added "Reload" context menu to reload children. To check old child nodes removing for crash.
import random
import sys
import weakref
from enum import Enum, auto
from typing import Optional, List
from PyQt5 import QtCore, QtTest
from PyQt5.QtWidgets import QMainWindow, QTreeView, QVBoxLayout, QApplication, QMenu
from PyQt5.QtCore import QModelIndex, Qt
class TreeNodeStatus(Enum):
NotPopulated = auto()
Populating = auto()
Populated = auto()
Error = auto()
class TreeNode(QtCore.QObject):
""" Node of objects tree; root node is essentially a data model """
status_changed = QtCore.pyqtSignal(object) # node
before_children_added = QtCore.pyqtSignal(object, int, int) # parent_node, pos, count
children_added = QtCore.pyqtSignal(object, int, int) # parent_node, pos, count
before_children_removed = QtCore.pyqtSignal(object, int, int) # parent_node, pos, count
children_removed = QtCore.pyqtSignal(object, int, int) # parent_node, pos, count
_populate_request = QtCore.pyqtSignal()
def __init__(self, name: str, parent: Optional['TreeNode']):
super().__init__()
self._name = name
self._parent_ref = weakref.ref(parent) if parent is not None else lambda: None
self._status: TreeNodeStatus = TreeNodeStatus.NotPopulated
self._children: List[TreeNode] = []
# to listen root node signals only
if parent is not None:
self.status_changed.connect(parent.status_changed)
self.before_children_added.connect(parent.before_children_added)
self.children_added.connect(parent.children_added)
self.before_children_removed.connect(parent.before_children_removed)
self.children_removed.connect(parent.children_removed)
# to imitate minimal delay between fetchMore > populate call stack and adding/removing nodes;
# for nodes that can be created immediately in populate direct calls
# fetchMore > populate > _on_children_received causes crash;
# using of this signal prevents crash
self._populate_request.connect(self._populate, Qt.ConnectionType.QueuedConnection)
# for nodes that can not be created immediately in populate;
# to imitate delay due to getting response to a request
self._timer = QtCore.QTimer()
self._timer.setSingleShot(True)
self._timer.setInterval(2 * 1000) # 2s
self._timer.timeout.connect(self._on_children_received)
def parent(self) -> Optional['TreeNode']:
return self._parent_ref()
#property
def status(self) -> TreeNodeStatus:
return self._status
def _set_status(self, status: TreeNodeStatus):
self._status = status
self.status_changed.emit(self)
def populate(self):
# # signal with QueuedConnection - works good
# self._populate_request.emit()
# direct call causes app crash for nodes that can be created immediately and if there is no proxy model
self._populate()
def _populate(self):
# loading was started for this node already, exit
if self.status == TreeNodeStatus.Populating:
return
# start loading
self._set_status(TreeNodeStatus.Populating)
# forget old children
old_children_count = len(self._children)
self.before_children_removed.emit(self, 0, old_children_count)
# disconnect signals
for child in self._children:
child.status_changed.disconnect(self.status_changed)
child.before_children_added.disconnect(self.before_children_added)
child.children_added.disconnect(self.children_added)
child.before_children_removed.disconnect(self.before_children_removed)
child.children_removed.disconnect(self.children_removed)
self._children.clear()
self.children_removed.emit(self, 0, old_children_count)
# request data about children nodes
# # timer - for nodes that can not be created immediately
# self._timer.start()
# direct call - for nodes that can be created immediately
self._on_children_received()
def children(self) -> List['TreeNode']:
return self._children
#property
def name(self) -> str:
return self._name
def _on_children_received(self):
print('!_on_children_received', self.name)
# create children nodes
new_children_count = random.randint(0, 4)
self.before_children_added.emit(self, 0, new_children_count)
self._children = [TreeNode(self.name + ' ' + str(i), self) for i in range(new_children_count)]
self.children_added.emit(self, 0, new_children_count)
# update status
self._set_status(TreeNodeStatus.Populated)
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, root_node: TreeNode):
super().__init__()
# root node == data model
self._root_node = root_node
self._root_node.status_changed.connect(self._on_node_status_changed)
self._root_node.before_children_added.connect(self._before_children_added)
self._root_node.children_added.connect(self._on_children_added)
self._root_node.before_children_removed.connect(self._before_children_removed)
self._root_node.children_removed.connect(self._on_children_removed)
def index(self, row: int, column: int, parent=QModelIndex(), *args, **kwargs) -> QModelIndex:
# discard non-existent indices: check for row/column for given parent inside
if not self.hasIndex(row, column, parent):
return QModelIndex()
# get parent node by index
if parent is None or not parent.isValid():
parent_node: TreeNode = self._root_node
else:
parent_node: TreeNode = parent.internalPointer()
# if has child with given row
if row < len(parent_node.children()):
# create index with node as internalPointer
return self.createIndex(row, column, parent_node.children()[row])
return QModelIndex()
def parent(self, index: QModelIndex = None) -> QModelIndex:
# invalid index => root node
if not index.isValid():
return QModelIndex()
node: TreeNode = index.internalPointer()
parent_node: TreeNode = node.parent()
# if parent is root node, return invalid index
if parent_node is self._root_node:
return QModelIndex()
# get row of parent node; parent_node is not root, must have it's own parent
grandparent_node = parent_node.parent()
parent_row = grandparent_node.children().index(parent_node)
# create index with node as internalPointer
return self.createIndex(parent_row, 0, parent_node)
def hasChildren(self, parent=QModelIndex(), *args, **kwargs) -> bool:
# can we expand node? if we can we have a triangle to the left of the node
parent_node = self._node_from_index(parent)
# children loaded - look at the number
if parent_node.status == TreeNodeStatus.Populated:
return len(parent_node.children()) > 0
# error - no children, can't expand
elif parent_node.status == TreeNodeStatus.Error:
return False
# not loaded/loading - assume they are
else:
return True
def canFetchMore(self, parent: QModelIndex) -> bool:
# can we get more data (child nodes) for parent?
# print('canFetchMore!', self._node_from_index(parent).name)
return self._can_fetch_more(parent)
def _can_fetch_more(self, parent: QModelIndex) -> bool:
parent_node = self._node_from_index(parent)
# children are not loaded/loading - assume they are
if parent_node.status == TreeNodeStatus.NotPopulated:
return True
# in other cases - can not get more child nodes
elif parent_node.status in [TreeNodeStatus.Populating,
TreeNodeStatus.Populated,
TreeNodeStatus.Error]:
return False
assert False
def fetchMore(self, parent: QModelIndex) -> None:
# get more data (child nodes) for parent
print('!FetchMore', self._node_from_index(parent).name)
if not self._can_fetch_more(parent):
return
parent_node = self._node_from_index(parent)
if parent_node.status != TreeNodeStatus.Populating:
parent_node.populate()
def rowCount(self, parent=QModelIndex(), *args, **kwargs):
parent_node = self._node_from_index(parent)
return len(parent_node.children())
def columnCount(self, parent=None, *args, **kwargs):
return 1
def _node_from_index(self, index: Optional[QModelIndex]) -> TreeNode:
# invalid index - root node
if index is None or not index.isValid():
return self._root_node
else:
return index.internalPointer()
def _index_from_node(self, node: TreeNode) -> Optional[QModelIndex]:
# root node - invalid index
if node is self._root_node:
return QModelIndex()
# according to the principle from index method
parent_node = node.parent()
row = parent_node.children().index(node)
return self.createIndex(row, 0, node)
def data(self, index, role=None):
node = self._node_from_index(index)
if role == Qt.DisplayRole:
return node.name
# get nodes by UserRole
elif role == Qt.UserRole:
return node
elif role == Qt.DecorationRole:
pass
def _on_node_status_changed(self, node: TreeNode):
index = self._index_from_node(node)
if index is not None:
# notify about changes - icon, tooltip
self.dataChanged.emit(index, index, [Qt.DecorationRole, Qt.ToolTipRole])
def _before_children_removed(self, parent_node: TreeNode, pos: int, count: int):
parent_index = self._index_from_node(parent_node)
if parent_index is not None:
self.beginRemoveRows(parent_index, pos, pos + count - 1)
def _on_children_removed(self, parent_node: TreeNode, pos: int, count: int):
self.endRemoveRows()
def _before_children_added(self, parent_node: TreeNode, pos: int, count: int):
parent_index = self._index_from_node(parent_node)
if parent_index is not None:
self.beginInsertRows(parent_index, pos, pos + count - 1)
print('!beginInsertRows', parent_node.name)
def _on_children_added(self, parent_node: TreeNode, pos: int, count: int):
self.endInsertRows()
print('!endInsertRows', parent_node.name)
class TreeView(QTreeView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._menu = QMenu(self)
# reload child nodes
self._reload_act = self._menu.addAction('Reload')
self._reload_act.triggered.connect(self._on_reload)
def mouseReleaseEvent(self, event):
""" Call context menu on right click button release """
super().mouseReleaseEvent(event)
if event.button() == Qt.MouseButton.RightButton:
index = self.indexAt(event.pos())
# above nodes only
if index.isValid():
self._menu.popup(self.viewport().mapToGlobal(event.pos()))
def _on_reload(self):
index = self.currentIndex()
node = index.data(role=Qt.UserRole)
if node.status != TreeNodeStatus.Populating:
node.populate()
class ClientWindow(QMainWindow):
def __init__(self):
super().__init__()
self._setup_ui()
root_node = TreeNode('root', None)
model = TreeModel(root_node)
# proxy = QtCore.QSortFilterProxyModel()
# proxy.setSourceModel(model)
# FixMe crash on expanding any node if we put source model here
self._view.setModel(model)
def _setup_ui(self):
self._view = TreeView()
self._view.setSortingEnabled(True)
central_wdg = self._view
central_vlt = QVBoxLayout()
central_wdg.setLayout(central_vlt)
self.setCentralWidget(central_wdg)
if __name__ == '__main__':
app = QApplication([])
main_window = ClientWindow()
main_window.show()
sys.exit(app.exec())
Related
I been looking over this Binary Tree code to figure out what it does:
class BinaryTree:
def __init__(self):
self.root = None
self.size = 0
# Insert element e into the binary search tree
# Return True if the element is inserted successfully
def insert(self, e):
if self.root == None:
self.root = self.createNewNode(e) # Create a new root
else:
# Locate the parent node
parent = None
current = self.root
while current != None:
if e < current.element:
parent = current
current = current.left
elif e > current.element:
parent = current
current = current.right
else:
return False # Duplicate node not inserted
# Create the new node and attach it to the parent node
if e < parent.element:
parent.left = self.createNewNode(e)
else:
parent.right = self.createNewNode(e)
self.size += 1 # Increase tree size
return True # Element inserted
# Create a new TreeNode for element e
def createNewNode(self, e):
return TreeNode(e)
# Return the size of the tree
def getSize(self):
return self.size
# Return true if the tree is empty
def isEmpty(self):
return self.size == 0
# Remove all elements from the tree
def clear(self):
self.root == None
self.size == 0
# Return the root of the tree
def getRoot(self):
return self.root
class TreeNode:
def __init__(self, e):
self.element = e
self.left = None # Point to the left node, default None
self.right = None # Point to the right node, default None
If I do the following code:
tree = BinaryTree()
tree.insert("500")
print(tree.getRoot)
print(tree.getRoot())
Result:
<bound method BinaryTree.getRoot of <__main__.BinaryTree object at 0x036E1E68>>
<__main__.TreeNode object at 0x036FA1F0>
I would not get the string "500" back even though its the root. Is there something missing from the getRoot function? I cannot find what is making the function being unable to return the string.
tree.getRoot is a method of the BinaryTree object.
And tree.getRoot() is calling the method and it returns a TreeNode object. If you want to get "500", you should get the element property of it.
So you can try the following code:
tree = BinaryTree()
tree.insert("500")
print(tree.getRoot().element)
which prints
500
My issue happens when I expand any item in my treeview(besides the top item), and pick its child. Then the top item also expands to show its children.
Example:
I have 2 children under root, each with its own child:
root
|-A
|-child AA
|
|-B
|-child BB
The issue: When I expand B and pick child BB in my treeview, it also opens(expands) A for some reason.
If we add another item under root, C with a child CC, and expand that instead of B; A will still expand as soon as child CC is picked.
I'm using a custom class as base items and I'm using my own class of QAbstractItemModel as sourceModel for the QSortFilterProxyModel. I'm sure the problem lies somewhere in those two custom classes but I can't figure why it happens.
I have a working example further down. It includes a few bits and pieces of the original script, so it might seem a bit messy.
The top treeview is using the proxy(and having the issue) and the one below is skipping the proxy and is not having any issue.
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.custom_nodes = [] #A list that will contain all our custom nodes.
#list of stand-in dicts:
self.all_node_list = [{'node_name': 'A', 'parent': None, 'children': [{'node_name': 'AB', 'parent': 'A','children': [{'node_name': 'ABC','parent': 'AB','children': []}]}]},
{'node_name': 'Prop', 'parent': None, 'children': [{'node_name': 'SubProp', 'parent': 'Prop','children': [{'node_name': 'Asset', 'parent': 'SubProp', 'children': []}]}]}]
self.MakeNodesFromDict(self.all_node_list,None)#Make custom nodes from dict
self.tree_model = CustomModel(self.custom_nodes) #Create custom model
self.CreateWindow()
def CreateWindow(self):
self.setWindowTitle("Top proxy - below normal")
self.main_layout = QtWidgets.QVBoxLayout()
#SEARCH BAR
self.menu_search_bar = QtWidgets.QLineEdit()
self.main_layout.addWidget(self.menu_search_bar)
#TREE VIEW USING PROXY MODEL
self.proxy_tree = QtWidgets.QTreeView()
self.proxy_tree.setObjectName("treeView")
self.proxy_tree.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.proxy_tree.setHeaderHidden(True)
self.proxy_tree.setExpandsOnDoubleClick(True)
self.proxy_tree.setAnimated(True)
self.proxy_tree.setIndentation(20)
self.proxy_tree.setSortingEnabled(True)
self.proxy_tree.setObjectName("proxy_treeView")
self.proxy_tree.setWindowTitle("Dir View")
#THE PROXY MODEL
self.proxyModel = QtCore.QSortFilterProxyModel(self.proxy_tree)
self.proxyModel.setSourceModel(self.tree_model)
self.proxyModel.setRecursiveFilteringEnabled(True)
self.proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.proxy_tree.setModel(self.proxyModel)
self.proxyModel.sort(0, 0)
#Connect proxy bar to tree
self.menu_search_bar.textChanged.connect(self.ProxyUpdate)
self.menu_search_bar.returnPressed.connect(self.SearchEnter)
#TREE VIEW WITHOUT PROXY MODEL
self.tree = QtWidgets.QTreeView()
self.tree.setObjectName("treeView")
self.tree.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.tree.setHeaderHidden(True)
self.tree.setExpandsOnDoubleClick(True)
self.tree.setAnimated(True)
self.tree.setIndentation(20)
self.tree.setSortingEnabled(True)
self.tree.setModel(self.tree_model)
self.main_layout.addWidget(self.proxy_tree)
self.main_layout.addWidget(self.tree)
self.setLayout(self.main_layout)
###########################################################
#################### FUNCTIONS ############################
###########################################################
def MakeNodesFromDict(self,cur_list, cur_parent):
for t in cur_list:
cur_node = CustomNode(node_name=t["node_name"], children=[],_parent=cur_parent)
if cur_parent:
cur_parent.addChild(cur_node)
self.MakeNodesFromDict(t["children"], cur_node)
self.custom_nodes.append(cur_node)
#QtCore.pyqtSlot(str) # <--Not sure if this is needed?
def ProxyUpdate(self,text):
my_pattern = QtCore.QRegularExpression()
my_pattern.setPatternOptions(QtCore.QRegularExpression.CaseInsensitiveOption)
my_pattern.setPattern(text)
self.proxyModel.setFilterRegularExpression(my_pattern)
def SearchEnter(self): #opens or closes the tree view after search
if self.menu_search_bar.text() == "":
self.proxy_tree.collapseAll()
else:
self.proxy_tree.expandAll()
class CustomModel(QtCore.QAbstractItemModel): #Custom model to work with custom nodes
def __init__(self, nodes): #Give all custom nodes at init
QtCore.QAbstractItemModel.__init__(self)
self._root = CustomNode() #Create root object of custom model as a custom node aswell
self.nodes = nodes
for node in self.nodes:
if node.parent()==None: #Go through and add nodes with no parents (top nodes)
self._root.addChild(node)
def emitDataChanged(self):
self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
def rowCount(self, index):
if index.isValid():
node = index.internalPointer()
return node.childCount()
return self._root.childCount()
def addChild(self, node, _parent):
if not _parent or not _parent.isValid():
parent = self._root
else:
parent = _parent.internalPointer()
parent.addChild(node)
def index(self, row, column, _parent=None):
if not _parent or not _parent.isValid():
node_parent = self._root
else:
node_parent = _parent.internalPointer()
if not QtCore.QAbstractItemModel.hasIndex(self, row, column, _parent):
return QtCore.QModelIndex()
child = node_parent.child(row)
if child:
return QtCore.QAbstractItemModel.createIndex(self, row, column, child)
else:
return QtCore.QModelIndex()
def parent(self, index):
if index.isValid():
p = index.internalPointer().parent()
if p:
return QtCore.QAbstractItemModel.createIndex(self, p.row(), 0, p)
return QtCore.QModelIndex()
def columnCount(self, index):
if index.isValid():
return index.internalPointer().columnCount()
return self._root.columnCount()
def data(self, index, role):
if not index.isValid():
return None
node = index.internalPointer()
if role == QtCore.Qt.DisplayRole:
return node.data(index.column())
return None
def getNode(self, index):
return index.internalPointer()
def update(self, index):
pass
class CustomNode(object):
def __init__(self, column_count=1,row=0,children=[],node_name="root",_parent=None):
self._column_count = column_count
self._row = row
self.node_name = node_name
self._parent = _parent
self._children = children
def addChild(self, child):
child._parent = self
child._row = self.childCount()
self._children.append(child)
self._column_count = max(child.columnCount(), self._column_count)
def GetName(self):
return self.node_name
def GetChildren(self):
return self._children
def data(self, column):
return self.node_name
def columnCount(self):
return self._column_count
def childCount(self):
return len(self._children)
def child(self, row):
if row >= 0 and row < self.childCount():
return self._children[row]
def parent(self):
return self._parent
def row(self):
return self._row
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
I had some trouble with working out how to subclass QAbstractItemModel and use your own object class as elements. So what you see here is half trial and error and half stackoverflow answers, all put together :)
I have been trying to update a QTableViewModel when inserting a new object that represents a row. I did follow the advise of several question in SO, but I cannot get an example to work.
After debugging, I found that the call to self.endInsertRows() produces the crash.
This is a minimal example:
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui, QtWidgets
class Wire:
def __init__(self, name, x, y, gmr, r):
self.name = name
self.x = x
self.y = y
self.r = r
self.gmr = gmr
class WiresCollection(QtCore.QAbstractTableModel):
def __init__(self, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.header = ['Name', 'R (Ohm/km)', 'GMR (m)']
self.index_prop = {0: 'name', 1: 'r', 2: 'gmr'}
self.wires = list()
def add(self, wire: Wire):
"""
Add wire
:param wire:
:return:
"""
row = len(self.wires)
self.beginInsertRows(QtCore.QModelIndex(), row, row)
self.wires.append(wire)
self.endInsertRows()
def delete(self, index):
"""
Delete wire
:param index:
:return:
"""
row = len(self.wires)
self.beginRemoveRows(QtCore.QModelIndex(), row, row)
self.wires.pop(index)
self.endRemoveRows()
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.wires)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.header)
def parent(self, index=None):
return QtCore.QModelIndex()
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.isValid():
if role == QtCore.Qt.DisplayRole:
val = getattr(self.wires[index.row()], self.index_prop(index.column()))
return str(val)
return None
def headerData(self, p_int, orientation, role):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self.header[p_int]
def setData(self, index, value, role=QtCore.Qt.DisplayRole):
"""
Set data by simple editor (whatever text)
:param index:
:param value:
:param role:
"""
wire = self.wires[index.row()]
attr = self.index_prop[index.column()]
setattr(wire, attr, value)
class TowerBuilderGUI(QtWidgets.QDialog):
def __init__(self, parent=None):
"""
Constructor
Args:
parent:
"""
QtWidgets.QDialog.__init__(self, parent)
self.setWindowTitle('Tower builder')
# GUI objects
self.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
self.layout = QVBoxLayout(self)
self.wires_tableView = QTableView()
self.add_wire_pushButton = QPushButton()
self.add_wire_pushButton.setText('Add')
self.delete_wire_pushButton = QPushButton()
self.delete_wire_pushButton.setText('Delete')
self.layout.addWidget(self.wires_tableView)
self.layout.addWidget(self.add_wire_pushButton)
self.layout.addWidget(self.delete_wire_pushButton)
self.setLayout(self.layout)
# Model
self.wire_collection = WiresCollection(self)
# set models
self.wires_tableView.setModel(self.wire_collection)
# button clicks
self.add_wire_pushButton.clicked.connect(self.add_wire_to_collection)
self.delete_wire_pushButton.clicked.connect(self.delete_wire_from_collection)
def msg(self, text, title="Warning"):
"""
Message box
:param text: Text to display
:param title: Name of the window
"""
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText(text)
# msg.setInformativeText("This is additional information")
msg.setWindowTitle(title)
# msg.setDetailedText("The details are as follows:")
msg.setStandardButtons(QMessageBox.Ok)
retval = msg.exec_()
def add_wire_to_collection(self):
"""
Add new wire to collection
:return:
"""
name = 'Wire_' + str(len(self.wire_collection.wires) + 1)
wire = Wire(name, x=0, y=0, gmr=0, r=0.01)
self.wire_collection.add(wire)
def delete_wire_from_collection(self):
"""
Delete wire from the collection
:return:
"""
idx = self.ui.wires_tableView.currentIndex()
sel_idx = idx.row()
if sel_idx > -1:
self.wire_collection.delete(sel_idx)
else:
self.msg('Select a wire in the wires collection')
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = TowerBuilderGUI()
window.show()
sys.exit(app.exec_())
As indicated in the comments you have 2 errors:
The first is when you press add because when you add a new item you have to refresh the view and that's why it's called data() method and it's where the error is shown in self.index_prop(index.column()), index_pro is a dictionary so you should use [] instead of ().
val = getattr(self.wires[index.row()], self.index_prop[index.column()])
Another error is generated by the line idx = self.ui.wires_tableView.currentIndex(), the ui does not exist and it is not necessary, sure it is a remnant of a previous code, to access wires_tableView as this is a member of the class not it is necessary to use an intermediary, you must access directly with self: idx = self.wires_tableView.currentIndex()
The above are typos and probably mark it so that the question is closed there is another error that is not, and that is the reason for my answer.
In the line self.beginRemoveRows(...) you must pass the row that you are going to remove but you are passing the row that does not exist:
row = len(self.wires)
self.beginRemoveRows(QtCore.QModelIndex(), row, row) # <---- row does not exist in the table
The solution is simple, change it by index:
def delete(self, index):
"""
Delete wire
:param index:
:return:
"""
self.beginRemoveRows(QtCore.QModelIndex(), index, index)
self.wires.pop(index)
self.endRemoveRows()
I am trying to develop a GUI to managed scientific results. For this, I would like to present result from two datasets. The user would have a visual reprentation to help him to compare those results in two QTableView.
Image : comparer Result,
I want to link lines from the two table, so they always be present face to face.
When order change in one table, the other will follow and adapt its order to still have the linked lines face to face.
Eventually i would like to add empty row face to a line that haven't a relative line in the other table.
I was think to use a QSortFilterProxyModel but I am not sure how to use it.
Edit
My Question seem to be not clear. I Formulate here. I find myself a solution so here a example of what I was looking.
On this example line I link line according to the name (bla, bli blo, blu). We see on the same line, tables present face to face line of result "bla" and "bli", because there are in both left model and right.
There is no "blu" in the right table. so i add a empty line.
Idem in the left table with "blo"
In this example item are sorted with "configuration" of the right table. Left table have to follow the order choose by the right table.
Here my code without the solution
class Result(object):
def __init__(self, headerOrder, infoResult):
for n, headerName in enumerate(headerOrder):
self.__setattr__(headerName, infoResult[n])
self.diff = self.reference_redshift - self.estimate
ModelClass
class Result_model(QtCore.QAbstractTableModel):
def __init__(self, header, parent=None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.__datas = []
self.__headers = header
def rowCount(self, parent=None):
return len(self.__datas)
def columnCount(self, parent=None):
return len(self.__headers)
def data(self, index, role):
if role == QtCore.Qt.ToolTipRole:
row = index.row()
column = index.column()
return "{}: {}".format(self.__headers[column], getattr(self.__datas[row], self.__headers[column]))
if role == QtCore.Qt.DisplayRole:
row = index.row()
column = index.column()
value = getattr(self.__datas[row], self.__headers[column])
return value
def headerData(self, section, orientation, role):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
if section < len(self.__headers):
return self.__headers[section]
else:
return "not implemented"
else:
return section
def supportedDragActions(self):
return QtCore.Qt.CopyAction
def supportedDropActions(self):
return Qt.CopyAction | Qt.MoveAction
def getResult(self, index):
row = index.row()
return self.__datas[row]
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))
attribut = self.__headers[Ncol]
self.__datas = sorted(
self.__datas, key=lambda x: getattr(x, attribut), reverse=(order == QtCore.Qt.DescendingOrder))
self.emit(QtCore.SIGNAL("layoutChanged()"))
def addResults(self, results):
self.beginInsertRows(QtCore.QModelIndex(), len(
self.__datas), len(self.__datas) + len(results))
for res in results:
self.__datas.append(res)
self.endInsertRows()
TableView only Drag
class TableResult(QtGui.QTableView):
def __init__(self, parent=None):
QtGui.QTableView.__init__(self, parent)
self.setDragEnabled(True)
self.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
self.header, self.aid_index = [["aid", "estimate", "reference_redshift", "diff", "amazed_executable_id", "amazed_configuration_id",
"astronomical_object_name", "star_forming_rate", "magnitude", "log_f_halpha", "emission_velocity_dispersion", "res_dir"], 0]
self.tag_info_result = ["aid", "estimate", "reference_redshift", "amazed_executable_id", "amazed_configuration_id",
"astronomical_object_name", "star_forming_rate", "magnitude", "log_f_halpha", "emission_velocity_dispersion", "res_dir"]
self.setItemDelegateForColumn(self.aid_index, ButtonDelegate(self))
self.parent = parent
def startDrag(self, dropAction):
if(self.parent is not None and hasattr(self.parent, "selection")):
# create mime data object
mime = QtCore.QMimeData()
# start drag
drag = QtGui.QDrag(self)
drag.setMimeData(mime)
drag.start(QtCore.Qt.CopyAction | QtCore.Qt.CopyAction)
else:
print("Drag impossible")
def mouseMoveEvent(self, event):
self.startDrag(event)
TableView with drop
class Selection_receiver(TableResult):
"Add the drop possibility from TableResult"
def __init__(self, setResultFunction, parent=None):
TableResult.__init__(self, parent)
self.setAcceptDrops(True)
self.setResultFunction = setResultFunction
def dragEnterEvent(self, event):
if (isinstance(event.source(), TableResult)):
event.accept()
event.acceptProposedAction()
else:
event.ignore()
def dropEvent(self, event):
print("dropEvent")
if (isinstance(event.source(), TableResult)):
event.acceptProposedAction()
model_result = event.source().parent.resModel
self.setResultFunction(model_result)
else:
event.ignore()
The Widget presenting the two Tables
class Comparater_result_widget(QtGui.QWidget):
"""
Present two table for easy comparaison.
"""
def __init__(self,parent=None):
super(self.__class__, self).__init__(parent)
self.setWindowTitle("Result Comparer")
main_layout = QtGui.QVBoxLayout()
receiverSplitter = QtGui.QSplitter()
receiverSplitter.setOrientation(QtCore.Qt.Horizontal)
self.left_receiver = Selection_receiver(self.setLeftResult)
receiverSplitter.addWidget(self.left_receiver)
self.right_receiver = Selection_receiver( self.setRightResult)
receiverSplitter.addWidget(self.right_receiver)
main_layout.addWidget(receiverSplitter)
self.left_receiver.horizontalScrollBar().valueChanged.connect(
self.right_receiver.horizontalScrollBar().setValue)
self.right_receiver.horizontalScrollBar().valueChanged.connect(
self.left_receiver.horizontalScrollBar().setValue)
self.left_receiver.verticalScrollBar().valueChanged.connect(
self.right_receiver.verticalScrollBar().setValue)
self.right_receiver.verticalScrollBar().valueChanged.connect(
self.left_receiver.verticalScrollBar().setValue)
self.right_results = None
self.left_results = None
self.setLayout(main_layout)
def setLeftResult(self, model_result):
print("setLeftResult []".format(model_result))
self.left_results = model_result
self.add_model_result(self.left_receiver, model_result)
def setRightResult(self, model_result):
print("setRightResult {}".format(model_result))
self.right_results = model_result
self.add_model_result(self.right_receiver, model_result)
def add_model_result(self, receiver, model_result):
receiver.setModel(model_result)
if(self.right_results is not None and self.left_results is not None):
self.link_result()
def link_result(self):
# parse the two model and link results if the have equal on one
# particular attribut
pass
def OnLeftChangeOrder(self):
# somthing like right_proxy.reorder(left_order)
pass
def OnRightChangeOrder(self):
# something link left_proxy.reorder(right_order)
pass
Here is my solution i created.
I use Two Proxy:
my own mapping proxy model ("MixRowProxyModel")
a almost normal QSortFilterProxyModel ("MySortProxyModel")
Source model is the model of MixRowProxyModel.
MixRowProxyModel is the model of MySortProxyModel
Table can be in two state, master or slave. If I sort the right table at one column, the right become master and the left is slave.
When a table is master, its MySortProxyModel is active, MixRowProxyModel is inactive
When a table is slave, its MySortProxyModel is inactive, MixRowProxyModel is active
When the two source model are set. I create a map between rows of the sources models. This map will not change when table are sorted.
When a table become slave i construct the mapping for its MixRowProxyModel (see method change in Comparater_result_widget). For that i use the initial mapping i created between modelsource.
class MixRowProxyModel(QtGui.QAbstractProxyModel):
def __init__(self, controller, side, parent=None):
QtGui.QAbstractProxyModel.__init__(self, parent=parent)
self.controller = controller
self.nbEmpty = 0
self.mapProxyToSource = None
self.isMaster = True
self.side = side
def setMap(self, mapping):
self.isMaster = False
self.mapProxyToSource = mapping
self.mapSourceToProxy = {v: k for k,
v in self.mapProxyToSource.items()}
def mapFromSource(self, sourceIndex):
#print("MixRowProxyModel Proxy Index model {}".format(sourceIndex.model()))
if(not sourceIndex.isValid()):
return self.index(-1, -1)
if(self.isMaster):
return self.index(sourceIndex.row(), sourceIndex.column(), parent=QtCore.QModelIndex)
else:
row = sourceIndex.row()
if(row in self.mapSourceToProxy):
return self.index(self.mapSourceToProxy[row], sourceIndex.column())
else:
print("Invalid sourceIndex {}".format(row))
return self.index(-1, -1)
def mapToSource(self, proxyIndex):
if(not proxyIndex.isValid()):
return self.sourceModel().index(-1, -1)
if(self.isMaster):
return self.sourceModel().index(proxyIndex.row(), proxyIndex.column())
else:
row = proxyIndex.row()
if(row in self.mapProxyToSource):
return self.sourceModel().index(self.mapProxyToSource[row], proxyIndex.column())
else:
# print("Invalid proxyIndex {}".format(row))
return self.sourceModel().index(-1, -1)
def rowCount(self, parent=None):
return self.sourceModel().rowCount() + self.nbEmpty
def columnCount(self, parent=None):
return self.sourceModel().columnCount()
def addEmptyRow(self):
print("addEmptyRow {}".format(self.side))
self.beginInsertRows(QtCore.QModelIndex(),
self.rowCount(), self.rowCount())
self.nbEmpty += 1
self.endInsertRows()
return -1
def parent(self, index):
return QtCore.QModelIndex()
def index(self, row, column, parent=QtCore.QModelIndex):
if(row >= self.rowCount() or row < 0 or column < 0 or column >= self.columnCount()):
return QtCore.QModelIndex()
return self.createIndex(row, column, parent)
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self.sourceModel().headerData(section, orientation, role)
else:
return section
def flags(self, index):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def data(self, index, role=QtCore.Qt.DisplayRole):
if(not index.isValid()):
return None
if(self.isMaster):
if(index.row() < self.sourceModel().rowCount()):
return self.sourceModel().data(index, role)
else:
return None
else:
if(not index.row() in self.mapProxyToSource):
return None
return self.sourceModel().data(self.sourceModel().index(self.mapProxyToSource[index.row()], index.column()), role)
the second ProxyModel
class MySortProxyModel(QtGui.QSortFilterProxyModel):
sortedSignal = QtCore.pyqtSignal(QtCore.Qt.SortOrder)
def sort(self, column, order):
self.sourceModel().isMaster = True
#print("Sort order : {} sort Role {}".format(order, self.sortRole()))
self.setSortRole(QtCore.Qt.DisplayRole)
super().sort(column, order)
self.sortedSignal.emit(order)
the Parent Widget controlling the two tables
class Comparater_result_widget(QtGui.QWidget):
def __init__(self, controller, parent=None):
super(self.__class__, self).__init__(parent)
self.controller = controller
self.setWindowTitle("Result Comparer")
main_layout = QtGui.QVBoxLayout()
receiverSplitter = QtGui.QSplitter()
receiverSplitter.setOrientation(QtCore.Qt.Horizontal)
self.left_receiver = Selection_receiver(
self.controller, self.setLeftResult)
receiverSplitter.addWidget(self.left_receiver)
self.right_receiver = Selection_receiver(
self.controller, self.setRightResult)
receiverSplitter.addWidget(self.right_receiver)
main_layout.addWidget(receiverSplitter)
self.left_receiver.horizontalScrollBar().valueChanged.connect(
self.right_receiver.horizontalScrollBar().setValue)
self.right_receiver.horizontalScrollBar().valueChanged.connect(
self.left_receiver.horizontalScrollBar().setValue)
self.left_receiver.verticalScrollBar().valueChanged.connect(
self.right_receiver.verticalScrollBar().setValue)
self.right_receiver.verticalScrollBar().valueChanged.connect(
self.left_receiver.verticalScrollBar().setValue)
self.right_source_model = None
self.right_proxy_model = None
self.right_proxy_model2 = None
self.left_source_model = None
self.left_proxy_model = None
self.left_proxy_model2 = None
self.setLayout(main_layout)
def setLeftResult(self, model_result):
self.left_source_model = model_result
self.left_proxy_model = MixRowProxyModel(
self.controller, "left", parent=self)
# self.left_proxy_model.sortedSignal.connect(self.onLeftChange)
self.left_proxy_model.setSourceModel(self.left_source_model)
self.left_proxy_model2 = MySortProxyModel(parent=self)
self.left_proxy_model2.sortedSignal.connect(self.onLeftChange)
self.left_proxy_model2.setSourceModel(self.left_proxy_model)
self.add_model_result(self.left_receiver, self.left_proxy_model2)
def setRightResult(self, model_result):
self.right_source_model = model_result
self.right_proxy_model = MixRowProxyModel(
self.controller, "right", parent=self)
self.right_proxy_model.setSourceModel(self.right_source_model)
# self.right_proxy_model.sortedSignal.connect(self.onRightChange)
self.right_proxy_model2 = MySortProxyModel(parent=self)
self.right_proxy_model2.sortedSignal.connect(self.onRightChange)
self.right_proxy_model2.setSourceModel(self.right_proxy_model)
self.add_model_result(self.right_receiver, self.right_proxy_model2)
def add_model_result(self, receiver, model_result):
receiver.setModel(model_result)
if(self.right_source_model is not None and self.left_source_model is not None):
self.link_result()
def link_result(self):
name_to_row = {}
for numSourceLeftRow in range(self.left_source_model.rowCount()):
res = self.left_source_model.getResultNumRow(numSourceLeftRow)
name = res.astronomical_object_name
if(name in name_to_row):
name_to_row[name][0].append(numSourceLeftRow)
else:
name_to_row[name] = ([numSourceLeftRow], [])
for numSourceRightRow in range(self.right_source_model.rowCount()):
res = self.right_source_model.getResultNumRow(numSourceRightRow)
name = res.astronomical_object_name
if(name in name_to_row):
name_to_row[name][1].append(numSourceRightRow)
else:
name_to_row[name] = ([], [numSourceRightRow])
self.mapLeftToRight = {} # key = leftRow; value = rightRow
self.mapRightToLeft = {} # key = rightRow; value = leftRow
for list_leftRow, list_rightRow in name_to_row.values():
if(len(list_rightRow) > 1):
print(
"Error more that index at right for same astronomical name {}".list_rightRow)
if(len(list_leftRow) > 1):
print(
"Error more that index at left for same astronomical name {}".list_leftRow)
if(len(list_leftRow) == 0):
leftRow = self.left_proxy_model.addEmptyRow()
else:
leftRow = list_leftRow[0]
if(len(list_rightRow) == 0):
rightRow = self.right_proxy_model.addEmptyRow()
else:
rightRow = list_rightRow[0]
self.mapLeftToRight[leftRow] = rightRow
self.mapRightToLeft[rightRow] = leftRow
self.left_receiver.rowCountChanged(
self.left_source_model.rowCount(), self.left_proxy_model.rowCount())
self.right_receiver.rowCountChanged(
self.right_source_model.rowCount(), self.right_proxy_model.rowCount())
print("Link Done : LtoR : {}; RtoL {}".format(
self.mapLeftToRight, self.mapRightToLeft))
def onRightChange(self, order):
print("RightChange")
self.change(self.left_source_model, self.left_proxy_model, self.left_proxy_model2, self.right_source_model, self.right_proxy_model,
self.right_proxy_model2, self.mapLeftToRight, self.left_receiver, order)
def onLeftChange(self, order):
print("LeftChange")
self.change(self.right_source_model, self.right_proxy_model, self.right_proxy_model2, self.left_source_model, self.left_proxy_model,
self.left_proxy_model2, self.mapRightToLeft, self.right_receiver, order)
def change(self, slave_source_model, slave_proxy_model,
slave_proxy_model2, master_source_model, master_proxy_model, master_proxy_model2,
map_slave_to_master, slave_receiver, order):
if(slave_source_model is not None):
slaveMapping = dict() # in slave table key = indexProxy , value = index Source
if(order == QtCore.Qt.AscendingOrder):
unlinkIndex = 0
else:
unlinkIndex = master_source_model.rowCount()
for slaveSourceRow in range(slave_source_model.rowCount()):
# this line is link to one in master, so we keep the same
# proxy number
master_source_row = map_slave_to_master[slaveSourceRow]
if(master_source_row != -1):
master_source_index = master_source_model.index(
master_source_row, 0)
master_proxy = master_proxy_model.mapFromSource(
master_source_index)
master_proxy2 = master_proxy_model2.mapFromSource(
master_proxy)
slaveProxyRow = master_proxy2.row() # same as master
else:
slaveProxyRow = unlinkIndex # we put it at the end or begining depending on order
unlinkIndex += 1
slaveMapping[slaveProxyRow] = slaveSourceRow
slave_proxy_model.layoutAboutToBeChanged.emit()
slave_proxy_model.setMap(slaveMapping)
slave_proxy_model2.setSortRole(
QtCore.Qt.InitialSortOrderRole) # proxy 2 is reinitialise
slave_proxy_model.layoutChanged.emit()
I'm trying to modify the excellent example of pyqt4 called "Editabletreemodel" but I have a problem I can't manage: after I add a new row in the model, how can I save or update the data I've inserted to a text file?
Or more in general, how is it possible to save data from the model/view into a file?
Thank you for your help.
# This is only needed for Python v2 but is harmless for Python v3.
import sip
sip.setapi('QVariant', 2)
from PyQt4 import QtCore, QtGui
import editabletreemodel
from ui_mainwindow import Ui_MainWindow
import sys, os, time
import paramiko
import threading
class TreeItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data
self.childItems = []
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def childNumber(self):
if self.parentItem != None:
return self.parentItem.childItems.index(self)
return 0
def columnCount(self):
return len(self.itemData)
def data(self, column):
return self.itemData[column]
def insertChildren(self, position, count, columns):
if position < 0 or position > len(self.childItems):
return False
for row in range(count):
data = [None for v in range(columns)]
item = TreeItem(data, self)
self.childItems.insert(position, item)
return True
def insertColumns(self, position, columns):
if position < 0 or position > len(self.itemData):
return False
for column in range(columns):
self.itemData.insert(position, None)
for child in self.childItems:
child.insertColumns(position, columns)
return True
def parent(self):
return self.parentItem
def removeChildren(self, position, count):
if position < 0 or position + count > len(self.childItems):
return False
for row in range(count):
self.childItems.pop(position)
return True
def removeColumns(self, position, columns):
if position < 0 or position + columns > len(self.itemData):
return False
for column in range(columns):
self.itemData.pop(position)
for child in self.childItems:
child.removeColumns(position, columns)
return True
def setData(self, column, value):
if column < 0 or column >= len(self.itemData):
return False
self.itemData[column] = value
return True
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, headers, data, parent=None):
super(TreeModel, self).__init__(parent)
rootData = [header for header in headers]
self.rootItem = TreeItem(rootData)
self.setupModelData(data.split("\n"), self.rootItem)
def columnCount(self, parent=QtCore.QModelIndex()):
return self.rootItem.columnCount()
def data(self, index, role):
if not index.isValid():
return None
if role != QtCore.Qt.DisplayRole and role != QtCore.Qt.EditRole:
return None
item = self.getItem(index)
return item.data(index.column())
def flags(self, index):
if not index.isValid():
return 0
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def getItem(self, index):
if index.isValid():
item = index.internalPointer()
if item:
return item
return self.rootItem
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.rootItem.data(section)
return None
def index(self, row, column, parent=QtCore.QModelIndex()):
if parent.isValid() and parent.column() != 0:
return QtCore.QModelIndex()
parentItem = self.getItem(parent)
childItem = parentItem.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def insertColumns(self, position, columns, parent=QtCore.QModelIndex()):
self.beginInsertColumns(parent, position, position + columns - 1)
success = self.rootItem.insertColumns(position, columns)
self.endInsertColumns()
return success
def insertRows(self, position, rows, parent=QtCore.QModelIndex()):
parentItem = self.getItem(parent)
self.beginInsertRows(parent, position, position + rows - 1)
success = parentItem.insertChildren(position, rows,
self.rootItem.columnCount())
self.endInsertRows()
return success
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = self.getItem(index)
parentItem = childItem.parent()
if parentItem == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parentItem.childNumber(), 0, parentItem)
def removeColumns(self, position, columns, parent=QtCore.QModelIndex()):
self.beginRemoveColumns(parent, position, position + columns - 1)
success = self.rootItem.removeColumns(position, columns)
self.endRemoveColumns()
if self.rootItem.columnCount() == 0:
self.removeRows(0, rowCount())
return success
def removeRows(self, position, rows, parent=QtCore.QModelIndex()):
parentItem = self.getItem(parent)
self.beginRemoveRows(parent, position, position + rows - 1)
success = parentItem.removeChildren(position, rows)
self.endRemoveRows()
return success
def rowCount(self, parent=QtCore.QModelIndex()):
parentItem = self.getItem(parent)
return parentItem.childCount()
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role != QtCore.Qt.EditRole:
return False
item = self.getItem(index)
result = item.setData(index.column(), value)
if result:
self.dataChanged.emit(index, index)
return result
def setHeaderData(self, section, orientation, value, role=QtCore.Qt.EditRole):
if role != QtCore.Qt.EditRole or orientation != QtCore.Qt.Horizontal:
return False
result = self.rootItem.setData(section, value)
if result:
self.headerDataChanged.emit(orientation, section, section)
return result
def setupModelData(self, lines, parent):
parents = [parent]
indentations = [0]
number = 0
while number < len(lines):
position = 0
while position < len(lines[number]):
if lines[number][position] != " ":
break
position += 1
lineData = lines[number][position:].trimmed()
if lineData:
# Read the column data from the rest of the line.
columnData = [s for s in lineData.split('\t') if s]
if position > indentations[-1]:
# The last child of the current parent is now the new
# parent unless the current parent has no children.
if parents[-1].childCount() > 0:
parents.append(parents[-1].child(parents[-1].childCount() - 1))
indentations.append(position)
else:
while position < indentations[-1] and len(parents) > 0:
parents.pop()
indentations.pop()
# Append a new item to the current parent's list of children.
parent = parents[-1]
parent.insertChildren(parent.childCount(), 1,
self.rootItem.columnCount())
for column in range(len(columnData)):
parent.child(parent.childCount() -1).setData(column, columnData[column])
number += 1
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
headers = ("Sendor Name", "Address", "Comments")
file = QtCore.QFile('./default.txt')
file.open(QtCore.QIODevice.ReadOnly)
model = TreeModel(headers, file.readAll())
file.close()
print model.invisibleRootItem()
self.view.setModel(model)
for column in range(model.columnCount(QtCore.QModelIndex())):
self.view.resizeColumnToContents(column)
self.exitAction.triggered.connect(QtGui.qApp.quit)
self.view.selectionModel().selectionChanged.connect(self.updateActions)
self.actionsMenu.aboutToShow.connect(self.updateActions)
self.insertRowAction.triggered.connect(self.insertRow)
self.insertColumnAction.triggered.connect(self.insertColumn)
self.removeRowAction.triggered.connect(self.removeRow)
self.removeColumnAction.triggered.connect(self.removeColumn)
self.insertChildAction.triggered.connect(self.insertChild)
self.callSensorsButton.clicked.connect(self.call_sensors)
self.updateActions()
self.view.expandAll()
self.view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.view.customContextMenuRequested.connect(self.openMenu)
self.connection = connection_thread()
self.connect(self.connection, QtCore.SIGNAL("started()"), self.start_progressBar)
self.connect(self.connection, QtCore.SIGNAL("finished()"), self.stop_progressBar)
self.connect(self.connection, QtCore.SIGNAL("terminated()"), self.stop_progressBar)
self.SaveListButton.clicked.connect(self.save_sensor_list)
self.pushButton.clicked.connect(self.prova)
def save_sensor_list(self):
index = self.view.selectionModel().currentIndex()
model = self.view.model()
print model.rootItem
for i in range(0, model.rootItem.rowCount()):
print model.child(i)
def prova(self):
index = self.view.selectionModel().currentIndex()
model = self.view.model()
print model.data(index,0)
def openMenu(self, position):
indexes = self.view.selectedIndexes()
model = self.view.model()
if len(indexes) > 0:
level = 0
index = indexes[0]
while index.parent().isValid():
index = index.parent()
level += 1
menu = QtGui.QMenu()
if level == 0:
menu.addAction(self.tr("Call all %ss" % (model.data(index,0))))
menu.addSeparator()
menu.addAction(self.tr("Add new sensor family"),self.insertRow)
menu.addAction(self.tr("Add new sensor"),self.insertChild)
elif level == 1:
menu.addAction(self.tr("Call this sensor"))
menu.addSeparator()
menu.addAction(self.tr("Add new sensor"),self.insertRow)
elif level == 2:
menu.addAction(self.tr("Edit object"))
menu.exec_(self.view.viewport().mapToGlobal(position))
def insertChild(self):
index = self.view.selectionModel().currentIndex()
model = self.view.model()
if model.columnCount(index) == 0:
if not model.insertColumn(0, index):
return
if not model.insertRow(0, index):
return
for column in range(model.columnCount(index)):
child = model.index(0, column, index)
model.setData(child, "[No data]", QtCore.Qt.EditRole)
if not model.headerData(column, QtCore.Qt.Horizontal).isValid():
model.setHeaderData(column, QtCore.Qt.Horizontal,
"[No header]", QtCore.Qt.EditRole)
self.view.selectionModel().setCurrentIndex(model.index(0, 0, index),
QtGui.QItemSelectionModel.ClearAndSelect)
self.updateActions()
def insertColumn(self, parent=QtCore.QModelIndex()):
model = self.view.model()
column = self.view.selectionModel().currentIndex().column()
# Insert a column in the parent item.
changed = model.insertColumn(column + 1, parent)
if changed:
model.setHeaderData(column + 1, QtCore.Qt.Horizontal,
"[No header]", QtCore.Qt.EditRole)
self.updateActions()
return changed
def insertRow(self):
index = self.view.selectionModel().currentIndex()
model = self.view.model()
if not model.insertRow(index.row()+1, index.parent()):
return
self.updateActions()
for column in range(model.columnCount(index.parent())):
child = model.index(index.row()+1, column, index.parent())
model.setData(child, "[No data]", QtCore.Qt.EditRole)
def removeColumn(self, parent=QtCore.QModelIndex()):
model = self.view.model()
column = self.view.selectionModel().currentIndex().column()
# Insert columns in each child of the parent item.
changed = model.removeColumn(column, parent)
if not parent.isValid() and changed:
self.updateActions()
return changed
def removeRow(self):
index = self.view.selectionModel().currentIndex()
model = self.view.model()
if (model.removeRow(index.row(), index.parent())):
self.updateActions()
def updateActions(self):
hasSelection = not self.view.selectionModel().selection().isEmpty()
self.removeRowAction.setEnabled(hasSelection)
self.removeColumnAction.setEnabled(hasSelection)
hasCurrent = self.view.selectionModel().currentIndex().isValid()
self.insertRowAction.setEnabled(hasCurrent)
self.insertColumnAction.setEnabled(hasCurrent)
if hasCurrent:
self.view.closePersistentEditor(self.view.selectionModel().currentIndex())
row = self.view.selectionModel().currentIndex().row()
column = self.view.selectionModel().currentIndex().column()
if self.view.selectionModel().currentIndex().parent().isValid():
self.statusBar().showMessage("Position: (%d,%d)" % (row, column))
else:
self.statusBar().showMessage("Position: (%d,%d) in top level" % (row, column))
def start_progressBar(self):
self.progressBar.setRange(0,0)
self.progressBar.setValue(0)
def stop_progressBar(self):
self.progressBar.setRange(0,1)
self.progressBar.setValue(1)
def call_sensors(self):
self.textEdit.insertPlainText("Connecting to Fox...\n")
self.connection.start_thread(self.textEdit)
class connection_thread(QtCore.QThread):
def __init__(self, parent = None):
QtCore.QThread.__init__(self, parent)
def start_thread(self,textEdit):
self.textEdit = textEdit
self.start()
def run(self):
print "Dentro il thread"
time.sleep(10)
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('192.168.0.90', username='root', password='netusg20')
self.textEdit.insertPlainText("Calling sensor list...\n")
app.processEvents()
stdin, stdout, stderr = ssh.exec_command('python g20.py c')
self.textEdit.insertPlainText(stdout.read())
self.textEdit.insertPlainText(stderr.read())
self.textEdit.insertPlainText("Connection closed\n")
ssh.close()
app.processEvents()
except:
self.textEdit.insertPlainText(str(sys.exc_info()[1]))
ssh.close()
app.processEvents()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
I guess the easies way would be to iterate though model items and save save to file via QDataStream. QDataStream supports reading\writing QVariant's and you can get\set the model item's data as QVariant. Below is a small example, I'm using QStandardItemModel for simplicity:
import sys
from PyQt4 import QtGui, QtCore
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.setMinimumSize(400, 400)
# create model
self.model = QtGui.QStandardItemModel()
for k in range(0, 4):
parentItem = self.model.invisibleRootItem()
for i in range(0, 4):
item = QtGui.QStandardItem(QtCore.QString("item %0 %1").arg(k).arg(i))
parentItem.appendRow(item)
parentItem = item
# create treeview
self.view = QtGui.QTreeView(self)
self.view.setModel(self.model)
self.view.setMinimumSize(300, 400)
self.saveButton = QtGui.QPushButton("save", self)
self.saveButton.move(300, 1)
self.saveButton.clicked.connect(self.on_save_button_clicked)
self.layout = QtGui.QVBoxLayout(self.centralWidget())
self.layout.addWidget(self.view)
self.layout.addWidget(self.saveButton)
def on_save_button_clicked(self):
# create text file
file = QtCore.QFile("save.txt")
file.open(QtCore.QIODevice.WriteOnly)
# open data stream
out = QtCore.QDataStream(file)
# recursively write model item into the datastream
self.save_item(self.model.invisibleRootItem(), out)
def save_item(self, item, out):
for i in range(0, item.rowCount()):
child = item.child(i)
child.write(out)
self.save_item(child, out)
def main():
app = QtGui.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
hope this helps, regards