PySideのModel/Viewテンプレート

maya のリグに関するツールで、複数のオブジェクトをUIに貯めておいて一括で処理を行いたいとき、リストの制御にmodel/viewアーキテクチャを使います．
リストにオブジェクトを追加したり削除したりする手順についてテンプレートを作成しました

![2024-06-27_12h54_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125058/08c23470-4749-3f9b-fd62-ef5752a54d19.png)

model/viewについての詳細はこちら
https://doc.qt.io/qtforpython-6/overviews/model-view-programming.html
完全に理解するのは遠い道のりなので、qiitaを始め様々なサイトの知識を頂戴しました

```python
# -*- coding: utf-8 -*-

from maya import OpenMayaUI, cmds
from PySide2 import QtWidgets, QtCore, QtGui
import shiboken2

class MainUI(QtWidgets.QWidget):
    def __init__(self, parent = None):
        super(MainUI,self).__init__(parent)
        
        self.setWindowTitle('modlViewTemplate')
        self.resize(200,200)
        
        layout = QtWidgets.QVBoxLayout(self)
        layout.setContentsMargins(0,0,0,0)
        layout.setSpacing(0)

        self.item_list = ListWidget()
        button_refresh = QtWidgets.QPushButton('refresh')
        button_add     = QtWidgets.QPushButton('add')
        button_remove  = QtWidgets.QPushButton('remove')

        layout.addWidget(self.item_list)
        layout.addWidget(button_refresh)
        layout.addWidget(button_add)
        layout.addWidget(button_remove)

        button_refresh.clicked.connect(self.refreshItem)
        button_add.clicked.connect(self.appendItem)
        button_remove.clicked.connect(self.removeItem)

    # 選択したアイテムでリストを更新
    def refreshItem(self):
        self.item_list.listModel.clear()
        self.appendItem()

    # 選択したアイテムと既存のアイテムとの比較の差分をとって追加
    def appendItem(self):
        all_items = self.getItem()
        items = cmds.ls(sl=True)
        add_items = list(set(items)-set(all_items))
        self.item_list.listModel.addItems(add_items)

    def removeItem(self):
        sel_idx = self.item_list.listView.selectedIndexes()
        for idx in sel_idx:
            self.item_list.listModel.removeRow(idx.row())

    def getItem(self):
        all_items = []
        for i in range(self.item_list.listModel.rowCount()):
            data = self.item_list.listModel.item(i).data(0)
            all_items.append(data)
        return(all_items)

class ListView(QtWidgets.QListView):
    def __init__(self, *args, **keywords):
        super(ListView, self).__init__(*args, **keywords)
        self.setSelectionMode(QtWidgets.QListView.SingleSelection)#単独選択のみ
        self.setAlternatingRowColors(False)

class ListModel(QtGui.QStandardItemModel):
    def __init__(self,parent = None):
        super(ListModel, self).__init__(parent)

    # 複数アイテム追加のための関数
    def addItems(self, items):
        for i in items:
            item = QtGui.QStandardItem(i)
            self.appendRow(item)

class ListWidget(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super(ListWidget, self).__init__(*args, **kwargs)
        
        layout = QtWidgets.QVBoxLayout(self)
        layout.setContentsMargins(0,0,0,0)
        
        self.listView = ListView(self)
        layout.addWidget(self.listView)
        
        self.listModel = ListModel(self)
        self.listView.setModel(self.listModel)

def main():
    mayaWindow = shiboken2.wrapInstance(
        int(OpenMayaUI.MQtUtil.mainWindow()),QtWidgets.QWidget)
    
    existsWindow = mayaWindow.findChildren(QtWidgets.QWidget,'modelViewTmp')
    for w in existsWindow:
        w.close()
    
    ui = MainUI(mayaWindow)
    ui.setObjectName('modelViewTmp')
    ui.setWindowFlags(QtCore.Qt.Window|QtCore.Qt.WindowCloseButtonHint)
    ui.show()
```
QListViewを元にListViewクラスを作成します
setSelectionModeで選択のモードを設定します．今回はオブジェクトを削除するときに複数を一度に処理する方法ではないので、単独選択のみになっています．


```python
class ListView(QtWidgets.QListView):
    def __init__(self, *args, **keywords):
        super(ListView, self).__init__(*args, **keywords)
        self.setSelectionMode(QtWidgets.QListView.SingleSelection)#単独選択のみ
        self.setAlternatingRowColors(False)
```
QStandardItemModelを元にListModelクラスを作成します
複数アイテムを渡して追加する関数を付け加えています
mayaで取得したアイテムをQStandardItemに変換してappendRowで追加します

```python
class ListModel(QtGui.QStandardItemModel):
    def __init__(self,parent = None):
        super(ListModel, self).__init__(parent)

    # 複数アイテム追加のための関数
    def addItems(self, items):
        for i in items:
            item = QtGui.QStandardItem(i)
            self.appendRow(item)
```
UIに追加するためにQwidgetを元にしたオブジェクトにListVewを組み込んで、ListModelをsetします
```python
class ListWidget(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super(ListWidget, self).__init__(*args, **kwargs)
        
        layout = QtWidgets.QVBoxLayout(self)
        layout.setContentsMargins(0,0,0,0)
        
        self.listView = ListView(self)
        layout.addWidget(self.listView)
        
        self.listModel = ListModel(self)
        self.listView.setModel(self.listModel)
```
リスト内容の操作のための関数
**refresh**
clear関数でモデルの内容をクリアした後、後述のappendItem関数でアイテムを追加します
```python
def refreshItem(self):
    self.item_list.listModel.clear()
    self.appendItem()
```
**appendItem**
後述のgetItem関数でリスト内のすべてのアイテムを取得、選択していオブイジェクトとの差分をsetの比較で抽出した後ListModelに追加したaddItems関数を用いてモデルに追加します
```python
def appendItem(self):
    all_items = self.getItem()
    items = cmds.ls(sl=True)
    add_items = list(set(items)-set(all_items))
    self.item_list.listModel.addItems(add_items)
```
**removeItem**
リストで選択されているモデルデータから行番号を取得してアイテムを削除します
```python
def removeItem(self):
    sel_idx = self.item_list.listView.selectedIndexes()
    for idx in sel_idx:
        self.item_list.listModel.removeRow(idx.row())
```
**getItem**
リストの行数を取得後、各行のモデルデータのデータ内容をfor文で配列に追加して戻り値としてすべてのデータの文字の配列を返します
```python
    all_items = []
    for i in range(self.item_list.listModel.rowCount()):
        data = self.item_list.listModel.item(i).data(0)
        all_items.append(data)
    return(all_items)
```