I'm trying to write blender addons that it can create custom nodes and we can operate them as normal blender builin nodes, especially for grouping them together, in my options it is a very import function, but now the code can add custom node, but grouping still does not work, so any tips?
#
# architect.py -- the blender addon
#
import bpy
import nodeitems_utils
from nodeitems_utils import NodeCategory, NodeItem, NodeItemCustom
from bpy.types import NodeTree, ShaderNodeTree, Node, NodeGroup, NodeCustomGroup, NodeSocket
bl_info = {
"name": "Architect",
"author": "Lei Liu",
"category": "Node"}
class ArchitectEngine(bpy.types.RenderEngine):
bl_idname = 'ARCHITECT_RENDER'
bl_label = "Architect"
bl_use_preview = False
bl_use_shading_nodes = False
bl_use_exclude_layers = False
bl_use_save_buffers = False
draw_callbacks = {}
def __init__(self):
self.session = None
pass
def __del__(self):
pass
# main scene render
def update(self, data, scene):
pass
def render(self, scene):
pass
class ArchitectNodeTree(ShaderNodeTree):
bl_idname = 'ArchitectNodeTree'
bl_label = 'Architect Node Tree'
bl_icon = 'NODETREE'
nodetypes = {}
pass
#classmethod
def poll(cls, context):
return context.scene.render.engine == 'ARCHITECT_RENDER'
class ArchitectNodeGroup(NodeCustomGroup):
bl_idname = 'ArchitectNodeGroup'
bl_label = 'Architect Node Group'
node_tree = ArchitectNodeTree
#classmethod
def poll(cls, context):
return context.scene.render.engine == 'ARCHITECT_RENDER'
# Custom socket type
class ArchitectSocket(NodeSocket):
# Description string
'''Architect node socket type'''
# Optional identifier string. If not explicitly defined, the python class name is used.
bl_idname = 'ArchitectSocketType'
# Label for nice name display
bl_label = 'Architect Node Socket'
# Enum items list
my_items = [
("DOWN", "Down", "Where your feet are"),
("UP", "Up", "Where your head should be"),
("LEFT", "Left", "Not right"),
("RIGHT", "Right", "Not left")
]
myEnumProperty = bpy.props.EnumProperty(name="Direction", description="Just an example", items=my_items, default='UP')
# Optional function for drawing the socket input value
def draw(self, context, layout, node, text):
if self.is_output or self.is_linked:
layout.label(text)
else:
layout.prop(self, "myEnumProperty", text=text)
# Socket color
def draw_color(self, context, node):
return (1.0, 0.4, 0.216, 0.5)
class ArchitectTreeNode:
#classmethod
def poll(cls, ntree):
return ntree.bl_idname == 'ArchitectNodeTree'
class DemoNode(Node, ArchitectTreeNode):
bl_idname = 'DemoNodeType'
bl_label = 'Demo Node'
bl_icon = 'SOUND'
typename = 'DemoNodeType'
# === Custom Properties ===
# These work just like custom properties in ID data blocks
# Extensive information can be found under
# http://wiki.blender.org/index.php/Doc:2.6/Manual/Extensions/Python/Properties
myStringProperty = bpy.props.StringProperty()
myFloatProperty = bpy.props.FloatProperty(default=3.1415926)
# === Optional Functions ===
# Initialization function, called when a new node is created.
# This is the most common place to create the sockets for a node, as shown below.
# NOTE: this is not the same as the standard __init__ function in Python, which is
# a purely internal Python method and unknown to the node system!
def init(self, context):
self.inputs.new('ArchitectSocketType', "Hello")
self.inputs.new('NodeSocketFloat', "World")
self.inputs.new('NodeSocketVector', "!")
self.outputs.new('NodeSocketColor', "How")
self.outputs.new('NodeSocketColor', "are")
self.outputs.new('NodeSocketFloat', "you")
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
print("Removing node ", self, ", Goodbye!")
# Additional buttons displayed on the node.
def draw_buttons(self, context, layout):
layout.label("Node settings")
layout.prop(self, "myFloatProperty")
# Detail buttons in the sidebar.
# If this function is not defined, the draw_buttons function is used instead
def draw_buttons_ext(self, context, layout):
layout.prop(self, "myFloatProperty")
# myStringProperty button will only be visible in the sidebar
layout.prop(self, "myStringProperty")
# Optional: custom label
# Explicit user label overrides this, but here we can define a label dynamically
def draw_label(self):
return "I am a custom node"
class ArchitectNodeCategory(NodeCategory):
#classmethod
def poll(cls, context):
return (context.space_data.tree_type == 'ArchitectNodeTree')
# menu entry for node group tools
def group_tools_draw(self, layout, context):
layout.operator("node.group_make")
layout.operator("node.group_ungroup")
layout.separator()
# maps node tree type to group node type
node_tree_group_type = {
'CompositorNodeTree': 'CompositorNodeGroup',
'ShaderNodeTree': 'ShaderNodeGroup',
'TextureNodeTree': 'TextureNodeGroup',
'ArchitectNodeTree': 'ArchitectNodeGroup',
}
# generic node group items generator for shader, compositor and texture node groups
def node_group_items(context):
if context is None:
return
space = context.space_data
if not space:
return
ntree = space.edit_tree
if not ntree:
return
yield NodeItemCustom(draw=group_tools_draw)
def contains_group(nodetree, group):
if nodetree == group:
return True
else:
for node in nodetree.nodes:
if node.bl_idname in node_tree_group_type.values() and node.node_tree is not None:
if contains_group(node.node_tree, group):
return True
return False
for group in context.blend_data.node_groups:
if group.bl_idname != ntree.bl_idname:
continue
# filter out recursive groups
if contains_group(group, ntree):
continue
yield NodeItem(node_tree_group_type[group.bl_idname],
group.name,
{"node_tree": "bpy.data.node_groups[%r]" % group.name})
# only show input/output nodes inside node groups
def group_input_output_item_poll(context):
return False
architect_node_categories = [
ArchitectNodeCategory("ARCH_DEMO", "Demo", items=[
NodeItem("DemoNodeType"),
]),
ArchitectNodeCategory("ARCH_INPUT", "Input", items=[
NodeItem("TextureNodeCurveTime"),
NodeItem("TextureNodeCoordinates"),
NodeItem("TextureNodeTexture"),
NodeItem("TextureNodeImage"),
NodeItem("NodeGroupInput", poll=group_input_output_item_poll),
]),
ArchitectNodeCategory("ARCH_OUTPUT", "Output", items=[
NodeItem("NodeGroupOutput", poll=group_input_output_item_poll),
]),
ArchitectNodeCategory("ARCH_GROUP", "Group", items=node_group_items),
ArchitectNodeCategory("ARCH_LAYOUT", "Layout", items=[
NodeItem("NodeFrame"),
NodeItem("NodeReroute"),
]),
]
def register():
bpy.utils.register_class(ArchitectNodeTree)
bpy.utils.register_class(ArchitectNodeGroup)
bpy.utils.register_class(DemoNode)
nodeitems_utils.register_node_categories('ARCHITECT', architect_node_categories)
bpy.utils.register_module(__name__)
pass
def unregister():
nodeitems_utils.unregister_node_categories('ARCHITECT')
bpy.utils.unregister_class(ArchitectNodeGroup)
bpy.utils.unregister_class(ArchitectNodeTree)
bpy.utils.unregister_class(DemoNode)
bpy.utils.unregister_module(__name__)
pass
if __name__ == "__main__":
register()
Creating and editing node groups in a custom node tree needs to be implemented by the code that defines the custom node tree.
See this question for an example of implementing custom node groups.
Related
Let's say I added a TreeView widget called treeView in Qt Designer and I'm using this code to add a few items to it:
class StandardItem(Qt.QStandardItem):
def __init__(self, txt='', font_size=11, set_bold=False, color=Qt.QColor(0, 0, 0)):
super().__init__()
fnt = Qt.QFont('Open Sans', font_size)
fnt.setBold(set_bold)
self.setEditable(False)
self.setForeground(color)
self.setFont(fnt)
self.setText(txt)
model = Qt.QStandardItemModel()
rootNode = model.invisibleRootItem()
A = StandardItem("A")
A.appendRows([StandardItem("1"),StandardItem("2"),StandardItem("3")])
B = StandardItem("B")
B.appendRows([StandardItem("1"),StandardItem("2")])
rootNode.appendRows([A,B])
self.treeView.setModel(model)
How can I write a function that collapses/expands item A? And how can I write a function that selects item A, as if it has been clicked? I might be missing something in the docs.
Expansion and selection are tasks that affect the visual part so they must be handled by sight. The first task is done through the setExpanded() method and the second using the select() method of the selectionModel() associated with the view, in both cases the QModelIndex associated with the item is used:
import sys
from PyQt5 import Qt
class StandardItem(Qt.QStandardItem):
def __init__(self, txt="", font_size=11, set_bold=False, color=Qt.QColor(0, 0, 0)):
super().__init__()
fnt = Qt.QFont("Open Sans", font_size)
fnt.setBold(set_bold)
self.setEditable(False)
self.setForeground(color)
self.setFont(fnt)
self.setText(txt)
class MainWindow(Qt.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.treeView = Qt.QTreeView()
self.setCentralWidget(self.treeView)
self.model = Qt.QStandardItemModel()
rootNode = self.model.invisibleRootItem()
A = StandardItem("A")
A.appendRows([StandardItem("1"), StandardItem("2"), StandardItem("3")])
B = StandardItem("B")
B.appendRows([StandardItem("1"), StandardItem("2")])
rootNode.appendRows([A, B])
self.treeView.setModel(self.model)
index_A = A.index()
self.treeView.setExpanded(index_A, True)
self.treeView.selectionModel().select(index_A, Qt.QItemSelectionModel.Select)
def main():
app = Qt.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main()
Note:
It seems that the index() function of the QStandardItem has a bug since it sometimes returns invalid QModelIndex for valid QStandardItem.
print(A.child(0).index().isValid())
Output:
False
This is caused because when the child was added, the "A" was not part of the model then the model is null in those children, as can be seen with.
print(A.child(0).model())
Output:
None
If instead "A" are added to the model first and then the children are newly added, then the model is passed.
self.model = Qt.QStandardItemModel()
rootNode = self.model.invisibleRootItem()
A = StandardItem("A")
B = StandardItem("B")
rootNode.appendRows([A, B])
A.appendRows([StandardItem("1"), StandardItem("2"), StandardItem("3")])
B.appendRows([StandardItem("1"), StandardItem("2")])
print(A.child(0).model())
print(A.child(0).index().isValid())
Output:
<PyQt5.QtGui.QStandardItemModel object at 0x7fb40aa868b0>
True
In that case it is better to use the indexFromItem() method of QStandardItemModel:
self.treeView.selectionModel().select(
self.model.indexFromItem(A.child(0)), Qt.QItemSelectionModel.Select
)
I've noticed that QFormLayout in Pyside2 does not have the takeRow method like its PyQt5 counterpart. I've attempted to subclass QFormLayout to incorporate a similar method, but I've run into Runtime Errors, as the removal behavor of the LabelRole item is different than the FieldRole item. Another issue being that the LabelRole item does not actually get taken off the row even when the row itself is removed.
The following is the test sample I've been working with using Python 3.8.6:
from PySide2.QtWidgets import *
import sys
class MyFormLayout(QFormLayout):
def __init__(self, *args, **kwargs):
super(MyFormLayout, self).__init__(*args, **kwargs)
self.cache = []
print(f"Formlayout's identity: {self=}\nwith parent {self.parent()=}")
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row) ## <-- This seems necessary to make the rowCount() decrement. Alternative?
label_item.widget().setParent(None) ## <-- Runtime Error Here?
field_item.layout().setParent(None)
self.cache.append(label_item.widget(), field_item)
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label_item, field_item
def restoreRow(self, insert_idx: int):
print(f"Called {self.restoreRow.__name__}")
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
to_insert = self.cache.pop()
self.insertRow(insert_idx, to_insert[0], to_insert[1])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
class MyWindow(QWidget):
def __init__(self):
super(MyWindow, self).__init__()
self.mainlay = MyFormLayout(self)
self.cmb = QComboBox()
self.cmb.addItems(["Placeholder", "Remove 1 and 2"])
self.cmb.currentTextChanged.connect(self.remove_rows_via_combo)
self.current_text = self.cmb.currentText()
self.hlay1, self.le1, self.btn1 = self.le_and_btn(placeholderText="1")
self.hlay2, self.le2, self.btn2 = self.le_and_btn(placeholderText="2")
self.hlay3, self.le3, self.btn3 = self.le_and_btn(placeholderText="3")
self.hlay4, self.le4, self.btn4 = self.le_and_btn(placeholderText="4")
self.remove_btn = QPushButton("Remove", clicked=self.remove_row_via_click)
self.restore_btn = QPushButton("Restore", clicked=self.restore_a_row_via_click)
self.mainlay.addRow("Combobox", self.cmb)
for ii, hlayout in zip(range(1, 5), [self.hlay1, self.hlay2, self.hlay3, self.hlay4]):
self.mainlay.addRow(f"Row {ii}", hlayout)
self.mainlay.addRow(self.remove_btn)
self.mainlay.addRow(self.restore_btn)
#staticmethod
def le_and_btn(**kwargs):
hlay, le, btn = QHBoxLayout(), QLineEdit(**kwargs), QPushButton()
hlay.addWidget(le)
hlay.addWidget(btn)
return hlay, le, btn
def remove_row_via_click(self):
self.mainlay.takeRow(1)
def restore_a_row_via_click(self):
self.mainlay.restoreRow(1)
def remove_rows_via_combo(self, text):
print(f"{self.remove_rows_via_combo.__name__} received the text: {text}")
if text == "Remove 1 and 2":
self.mainlay.takeRow(1)
self.mainlay.takeRow(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec_())
I would like to understand why the behavior of the role items is different and how the method may be properly re-implemented.
The problem is that the label was created internally by Qt from a string, rather than by explicitly creating a QLabel in Python. This means that when the row is removed, the last remaining reference is also removed, which deletes the label on the C++ side. After that, all that's left on the Python side is an empty PyQt wrapper - so when you try to call setParent on it, a RuntimeError will be raised, because the underlying C++ part no longer exists.
Your example can therefore be fixed by getting python references to the label/field objects before the layout-item is removed:
class MyFormLayout(QFormLayout):
...
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
# get refs before removal
label = label_item.widget()
field = field_item.layout() or field_item.widget()
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row)
label.setParent(None)
field.setParent(None)
self.cache.append((label, field))
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label, field
Pardon me if the terminology of the title is wrong. Let me illustrate what I mean:
I have a data tree for a basic educational course. Every node holds 2 values(apart from children and parent data): ID and a bool value. I can not change the ID value since I am fetching it from an API so I can not form my data tree as a binary tree. That is what I mean by "does not have proper indexing".
BTW: ID is random.
As you can see, I am structuring my tree with a hierarchy. Courses are supersets of episodes and episodes are supersets of topics.
My question:
I want to gather some data from a specific topic node. I know it is a topic node and I know its id. How do I find that node to get some data out of it most efficiently?
When I researched about tree data structures, they usually index their nodes with some sort of rule (ex: binary trees), I am not sure I can do that since I want to preserve the hierarchy of data types. If there is a way that I can both keep some sort of indication of hierarchy and order my trees for many efficient queries. I am happy to do that. I don't want to brute force if possible
You can combine the usual Node class approach with a dictionary which you maintain at tree instance level.
For instance:
class Node:
def __init__(self, id, name, boolval):
self.id = id
self.name = name
self.boolval = boolval
self.children = []
def tolist(self): # utility method for simple visualisation
if not self.children:
return [self.name]
else:
return [self.name, [child.tolist() for child in self.children]]
class Tree:
def __init__(self):
self.dict = dict() # this will map id to node
self.root = None
def append(self, id, name, boolval, parentid=""):
node = Node(id, name, boolval)
self.dict[id] = node
if self.root:
parent = self.get(parentid)
parent.children.append(node)
else:
self.root = node
def get(self, id):
return self.dict[id]
def tolist(self):
if not self.root:
return
return self.root.tolist()
# demo use:
tree = Tree()
tree.append("ab12", "Tree", False)
tree.append("ab13", "Physics", False, "ab12")
tree.append("a4d5", "Math", False, "ab12")
tree.append("524d", "Section A", False, "a4d5")
# ...etc
print(tree.tolist())
If you need to know from a given node what its hierarchy is, then add parent references:
class Node:
def __init__(self, id, name, boolval):
self.id = id
self.name = name
self.boolval = boolval
self.children = []
self.parent = None
def path(self):
if self.parent:
return self.parent.path() + [self.id]
return [self.id]
def tolist(self): # utility method for simple visualisation
if not self.children:
return [self.name]
else:
return [self.name, [child.tolist() for child in self.children]]
class Tree:
def __init__(self):
self.dict = dict() # this will map id to node
self.root = None
def append(self, id, name, boolval, parentid=""):
node = Node(id, name, boolval)
self.dict[id] = node
if self.root:
parent = self.get(parentid)
parent.children.append(node)
node.parent = parent
else:
self.root = node
def get(self, id):
return self.dict[id]
def tolist(self):
if not self.root:
return
return self.root.tolist()
# demo use:
tree = Tree()
tree.append("ab12", "Tree", False)
tree.append("ab13", "Physics", False, "ab12")
tree.append("a4d5", "Math", False, "ab12")
tree.append("524d", "Section A", False, "a4d5")
# ...etc
print(tree.tolist())
print(tree.get("524d").path()) # ['ab12', 'a4d5', '524d']
When I provide an address /and or location to the entry bar and I press the "Get forecast" button the script fails at line 22. I think the error is raised because the str(address.get()) cant find the address variable, probably because it doesn't technically exist during that point of run time (I'm not able to log the error due to the structure of that function).
My question is; How do I make sure that my "get_hourly_forecast" function is able to access the address entry variable?
I have tried instantiating the address variable in various locations, e.g in the MainWeatherHub class, as well as in the MyWeatherApp class and then passing it as an argument to the MainWeatherHub in line 79, neither variation has worked. The current code shows the former variation.
import urllib, json, requests
from tkinter import *
from tkinter import ttk
def get_hourly_forecast(*args):
## params *args:
#A location argument
#Returns:
# A list of temps in Farenheit for the next 156 hours
API_KEY = 'removing my API key for security purposes'
try:
print('here') # The code makes it to here
curr_address = str(address.get()) # Code seems to fail here (not sure how to have the error print)
print('here')
geocode_url = "https://maps.googleapis.com/maps/api/geocode/json?address={}&key={}".format(cur_address, API_KEY)
response = requests.get(geocode_url)
response_dict = response.json()['results']
location = response_dict[0]['geometry']['location']
lat = location['lat']
lng = location['lng']
local_url_request = 'https://api.weather.gov/points/lat={}lng={}'.format(lat, lng)
response_one = requests.get(local_url_request)
json_dict_one = response_one.json()
local_props = json_dict_one['properties']
local_forecast_request = local_props['forecastHourly']
resposne_two = requests.get(local_forecast_request)
json_dict_two = resposne_two.json()
local_forecast_properites = json_dict_two['properties']
hourly_updates = local_forecast_properites['periods']
out = []
for i in hourly_updates:
for key, value in i.items():
if key == "temperature":
out.append(value)
current_weather.set(out[0])
except:
print("Not working.")
#############################################################
class MyWeatherApp:
"""
MyWeatherApp is the primary Frame for this GUI application
"""
def __init__(self, master):
super(MyWeatherApp, self).__init__()
self.master = master
# Create the main window Frame
master_style = ttk.Style()
master_style.configure('Master.TFrame')
self.master.title("My Weather")
self.master.geometry("500x500")
MWA = ttk.Frame(self.master, style='Master.TFrame')
MWA.place(relheight=1.0, relwidth=1.0)
# Run other widgets within this class
MainWeatherHub(MWA)
#############################################################
class MainWeatherHub(MyWeatherApp):
"""
The MainWeatherHub (MWH) is the top panel of the app
"""
def __init__(self, mainwindow):
super(MyWeatherApp, self).__init__()
self.mainwindow = mainwindow
# Create a Frame for the MainWeatherHub
MWH_style = ttk.Style()
MWH_style.configure('MWH.TFrame')
MWH = ttk.Frame(self.mainwindow, style='MWH.TFrame', relief='sunken')
MWH.place(relheight=0.33, relwidth=0.95, relx=0.025, rely=0.025)
# Create an entry widget to take a location
# and store that as a loction variable.
address = StringVar()
loc_entry = ttk.Entry(MWH, textvariable=address)
loc_entry.place(relheight=0.30, relwidth=.95, relx=0.025, rely=0.05)
# Get weather button finds weather for the users location
current_weather = StringVar()
get_weather_button = ttk.Button(loc_entry, text="Get Forecast", command=get_hourly_forecast)
get_weather_button.place(relheight=0.85,relwidth=0.2, relx=0.79, rely=0.075)
#Display weather in the Message widget
weath_display = Message(MWH, textvariable=current_weather)
weath_display.place(relwidth=0.95, relheight=0.55, relx=0.025, rely=0.375)
root = Tk()
my_gui = MyWeatherApp(root)
root.mainloop()
If this script works properly, it should return the current temperature in degrees Fahrenheit of the location that was provided in the entry bar.
You should send it as parameter
def get_hourly_forecast(cur_address):
geocode_url = "...".format(cur_address, API_KEY)
And then assing to button function which runs get_hourly_forecast with string
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
self.address = StringVar() # use self.
ttk.Button(loc_entry, text="Get Forecast", command=run_it)
def run_it(self):
get_hourly_forecast(self.address.get())
or using lambda
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
ttk.Button(loc_entry, text="Get Forecast", command=lambda:get_hourly_forecast(address.get()))
EDIT:
I see you use current_weather (StringVar from MainWeatherHub) in get_hourly_forecast to set value current_weather.set(out[0]).
You could send current_weather to get_hourly_forecast as parameter
def get_hourly_forecast(cur_address, current_weather):
geocode_url = "...".format(cur_address, API_KEY)
current_weather.set(out[0])
and
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
self.address = StringVar() # use self.
self.current_weather = StringVar() # use self.
ttk.Button(loc_entry, text="Get Forecast", command=run_it)
def run_it(self):
get_hourly_forecast(self.address.get(), self.current_weather)
but it could be better to return value from get_hourly_forecast
def get_hourly_forecast(cur_address):
geocode_url = "...".format(cur_address, API_KEY)
return out[0]
and get it in run_it
def run_it(self):
result = get_hourly_forecast(self.address.get())
if result is not None:
self.current_weather.set(result)
This way get_hourly_forecast doesn't work with StringVar and you can use it in other program which doesn't use StringVar.
I am trying to update listview model in QML quick controls with python. Every item in this model contains address and status property. Right now my code is this:
ListModel:
class ServersModel(QAbstractListModel):
def __init__(self, parent=None):
super(ServersModel, self).__init__(parent)
self.list = []
for address, status in (
("server 1", "green"),
("server 2", "green"),
("server 3", "red")):
self.list.append(server(address, status))
def rowCount(self, parent=QModelIndex()):
return len(self.list)
def data(self, index, role=None):
if role == Qt.DisplayRole: #show just the name
person = self.list[index.row()]
return QVariant(person.name)
elif role == Qt.UserRole: #return the whole python object
person = self.list[index.row()]
return person
return QVariant()
def removeRow(self, position):
self.list = self.list[:position] + self.list[position+1:]
self.reset()
ServerObject:
class server(object):
'''
a custom data structure, for example purposes
'''
def __init__(self, address, status):
self.address = address
self.status = status
def __repr__(self):
return "%s\n%s"% (self.address, self.status)
Python connection handler:
class ServersListViewHandler(QObject):
def __init__(self):
QObject.__init__(self)
listLoaded = pyqtSignal(QAbstractListModel, arguments=['model'])
data_changed = pyqtSignal(QModelIndex, QModelIndex)
# Slot for detecting clicks in listView
#pyqtSlot(str)
def listViewItemClicked(self, name):
self.listLoaded.emit(ServersModel())
And this is my connectin at qml:
Connections {
target: serversListViewHandler
onListLoaded: {
serversListView.model = model
}
}
Address property should go to text as value and status is color of circle. This works fine if I implement model directly to QML but I want to update it dynamically and load it form python. Any ideas on how to accomplish this? I am probably doing it totally wrong but there are almost none tutorial or references about pyqt 5
I'm working on a similar experiment at the moment. I succeeded doing this by using dedicated role names for columns (in my case in the list view).
For your example this would kinda look like this:
class ServersModel(QAbstractListModel):
AddressRole = Qt.UserRole + 1
StatusRole = Qt.UserRole + 2
_roles = {AddressRole: b"address", StatusRole: b"status"}
...
def roleNames(self):
return self._roles
def data(self, index, role=None):
if role == AddressRole: #show just the name
person = self.list[index.row()]
return QVariant(person.name)
elif role == StatusRole: #return the whole python object
person = self.list[index.row()]
return person
return QVariant()
Though this may only target the displaying part, not the complete update procedure. Would be interesting to see the QML where you access the model.
Also I'm not really sure if there is some kind of best practice for this kind of thing.