LoginSignup
13
18

More than 5 years have passed since last update.

PyQt5とpython3によるGUIプログラミング[4]

Last updated at Posted at 2016-07-09

MVCサンプル

ここの動画をお勧めします。ソースもついています。
PyQt4で書かれていますが、PyQt5に書き換えるのは難しくありません。
youTubeで「PyQt4 Model View Tutorial Part」と打ち込む事で動画がリストされます。

ここを参考にこれを作ってみました。

MyTableModel.py
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.Qt import *
import sys

class MyTableModel(QAbstractTableModel):
    def __init__(self, list, headers = [], parent = None):
        QAbstractTableModel.__init__(self, parent)
        self.list = list
        self.headers = headers

    def rowCount(self, parent):
        return len(self.list)

    def columnCount(self, parent):
        return len(self.list[0])

    def flags(self, index):
        return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable

    def data(self, index, role):
        if role == Qt.EditRole:
            row = index.row()
            column = index.column()
            return self.list[row][column]

        if role == Qt.DisplayRole:
            row = index.row()
            column = index.column()
            value = self.list[row][column]
            return value

    def setData(self, index, value, role = Qt.EditRole):
        if role == Qt.EditRole:
            row = index.row()
            column = index.column()
            self.list[row][column] = value
            self.dataChanged.emit(index, index)
            return True
        return False

    def headerData(self, section, orientation, role):

        if role == Qt.DisplayRole:

            if orientation == Qt.Horizontal:

                if section < len(self.headers):
                    return self.headers[section]
                else:
                    return "not implemented"
            else:
                return "item %d" % section

if __name__ == '__main__':

    app = QApplication(sys.argv)
    app.setStyle("plastique")

    listView = QListView()
    listView.show()

    comboBox = QComboBox()
    comboBox.show()


    tableView = QTableView()
    tableView.show()

    headers = ["000", "001", "002"]
    tableData0 = [
                 ['abc',100,200],
                 ['fff',130,260],
                 ['jjj',190,300],
                 ['ppp',700,500],
                 ['yyy',800,900]
                 ]

    model = MyTableModel(tableData0, headers)

    listView.setModel(model)
    comboBox.setModel(model)
    tableView.setModel(model)

    sys.exit(app.exec_())

スクリーンショット 2016-07-09 10.05.51.png

書き換えも可能です。

スクリーンショット 2016-07-09 10.09.22.png

数値のところはデフォルトのDelegateが働いてスピンボックスになっています。

ComboBoxのDelegate

ComboDelegate.py
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class ComboDelegate(QItemDelegate):
    comboItems=['コンボ--0', 'コンボ--1','コンボ--2']
    def createEditor(self, parent, option, proxyModelIndex):
        combo = QComboBox(parent)
        combo.addItems(self.comboItems)
        combo.currentIndexChanged.connect(self.currentIndexChanged)
        return combo

    def setModelData(self, combo, model, index):
        comboIndex=combo.currentIndex()
        text=self.comboItems[comboIndex]        
        model.setData(index, text)

    @pyqtSlot()
    def currentIndexChanged(self): 
        self.commitData.emit(self.sender())

class MyModel(QAbstractTableModel):
    def __init__(self, parent=None, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.items=['Item01','Item02','Item03']

    def rowCount(self, parent=QModelIndex()):
        return len(self.items)

    def columnCount(self, parent=QModelIndex()):
        return 1

    def data(self, index, role):        
        if not index.isValid(): return QVariant()

        row=index.row()
        item=self.items[row]

        if row>len(self.items): return QVariant()

        if role == Qt.DisplayRole:
            return QVariant(item) 

    def flags(self, index):
        return Qt.ItemIsEditable | Qt.ItemIsEnabled

    def setData(self, index, text):
        self.items[index.row()]=text

if __name__ == '__main__':
    app = QApplication(sys.argv)

    model = MyModel()
    tableView = QTableView()
    tableView.setModel(model)

    delegate = ComboDelegate()

    tableView.setItemDelegate(delegate)
    tableView.resizeRowsToContents()

    tableView.show()
    sys.exit(app.exec_())

スクリーンショット 2016-07-09 10.25.06.png

table model viewで新規作成、オープン、保存、行追加、行削除を実装してみた

保存はcsv形式です。
行の挿入は選択した行セルの上の行に追加します。
何も選択なしにボタンを押した場合は最終行の後に追加します。
削除は、基本選択した行、何も選択なしにボタンを押した場合は最終行を削除します。

とりあえずはサンプル提示で、解説は追ってしていきます。

View_TableModel.py
from PyQt5.QtGui import *
from PyQt5.Qt import *
import sys, csv

class Window (QWidget):
    def __init__ (self):
        QWidget.__init__(self, None)
        self.setWindowTitle('Table')

class View (Window):
    def __init__ (self, parent=None):
        super(View, self).__init__()

        #サイズ固定
        self.setFixedSize(850, 500)

        #Viewを作成
        self.tableView = QTableView()
        self.tableView.clicked.connect(self.viewClicked)

        #Viewの罫をブラックにする
        self.tableView.setStyleSheet("QTableView{gridline-color: black}")

        self.headers = ["▽", "AAAAAA", "BBBBBB", "CCCCCCC", "DDDDD", "EEEEE", "FFFFF", "GGGGG"]
        tableData0 = [
                     [QCheckBox(''), "ああああああ", "てすと", "テスト", "評価", "000000", "000000", 1],
                     ]

        #モデルを作成
        self.model = MyTableModel(tableData0, self.headers)
        self.tableView.setModel(self.model)

        #Insert、remove用の選択行に行の最大値をセット
        self.selectRow = self.model.rowCount(QModelIndex())

        #csv用のファイルフィルタをセット
        self.filters = "CSV files (*.csv)"

        #ファイル名を初期化
        self.fileName = None

        #ボタン作成
        self.buttonNew = QPushButton('NEW', self)
        self.buttonOpen = QPushButton('Open', self)
        self.buttonSave = QPushButton('Save', self)
        self.buttonAdd = QPushButton('add', self)
        self.buttonDell = QPushButton('Dell', self)

        #ボタングループをセット
        self.group = QButtonGroup()
        self.group.addButton(self.buttonNew)
        self.group.addButton(self.buttonOpen)
        self.group.addButton(self.buttonSave)
        self.group.addButton(self.buttonAdd)
        self.group.addButton(self.buttonDell)

        #Signal、Slotを設定
        self.buttonNew.clicked.connect(self.handleNew)
        self.buttonOpen.clicked.connect(self.handleOpen)
        self.buttonSave.clicked.connect(self.handleSave)
        self.buttonAdd.clicked.connect(self.insertRows)
        self.buttonDell.clicked.connect(self.removeRows)

        #水平レイアウトを設定
        layout = QHBoxLayout()

        layout.addWidget(self.buttonNew)
        layout.addWidget(self.buttonOpen)
        layout.addWidget(self.buttonSave)
        layout.addWidget(self.buttonAdd)
        layout.addWidget(self.buttonDell)

        #垂直レイアウトを設定
        Vlayout = QVBoxLayout()
        Vlayout.addWidget(self.tableView)
        Vlayout.addLayout(layout)

        #全体のレイアウトをセット
        self.setLayout(Vlayout)

    #保存処理→CSVで保存
    def handleSave(self):
        print("handleSave")
        if self.fileName == None or self.fileName == '':
            self.fileName, self.filters = QFileDialog.getSaveFileName(self, \
            filter=self.filters)
        if(self.fileName != ''):
            with open(self.fileName, 'wt') as stream:
                csvout = csv.writer(stream, lineterminator='\n')
                csvout.writerow(self.headers)
                for row in range(self.model.rowCount(QModelIndex())):
                    print(self.model.rowCount(QModelIndex()))
                    rowdata = []
                    for column in range(self.model.columnCount(QModelIndex())):
                        item = self.model.index( row, column, QModelIndex() ).data( Qt.DisplayRole )
                        if column == 0:
                            rowdata.append('')
                            continue

                        if item is not None:
                            rowdata.append(item)
                        else:
                            rowdata.append('')
                    csvout.writerow(rowdata)
                    print(rowdata)

    #ファイルオープン処理
    def handleOpen(self):
        print("handleOpen")
        self.fileName, self.filterName = QFileDialog.getOpenFileName(self)

        if self.fileName != '':
            with open(self.fileName, 'r') as f:
                reader = csv.reader(f)
                header = next(reader)
                buf = []
                for row in reader:
                    row[0] = QCheckBox("-")
                    buf.append(row)

                self.model = None
                self.model = MyTableModel(buf, self.headers)
                self.tableView.setModel(self.model)
                self.fileName = ''

    #新規作成
    def handleNew(self):
        print ("handleNew")
        self.fileName = ''

        defaultValue =[
        [QCheckBox(''), "ああああああ", "てすと", "テスト", "評価", "000000", "000000", 1]
        ]

        self.model = None
        self.model = MyTableModel(defaultValue, self.headers)
        print(defaultValue)
        self.tableView.setModel(self.model)

    #Viewとモデルに行追加→選択されている行の上に1行挿入します
    def insertRows(self, position, rows=1, index=QModelIndex()):
        print("position: %d"%position)
        print("rows: %d" % rows)
        print("rowCount: %d" % self.model.rowCount(QModelIndex()))

        position = self.selectRow
        self.model.beginInsertRows(QModelIndex(), position, position + rows - 1)
        for row in range(rows):
            self.model.list.insert(position, [QCheckBox(''), "ああああああ", "てすと", "テスト", "評価", "000000", "000000", 1])

        self.model.endInsertRows()
        return True

    #Viewとモデルから行削除→選択位置の行を削除します
    def removeRows(self, position, rows=1, index=QModelIndex()):
        print("Removing at position: %s"%position)
        position = self.selectRow
        self.model.beginRemoveRows(QModelIndex(), position, position + rows - 1)
        self.model.list = self.model.list[:position] + self.model.list[position + rows:]
        self.model.endRemoveRows()
        return True

    #Viewをクリックしたときの行の位置を取得
    def viewClicked(self, indexClicked):
        print('indexClicked() row: %s  column: %s'%(indexClicked.row(), indexClicked.column() ))
        self.selectRow = indexClicked.row()


class MyTableModel(QAbstractTableModel):

    def __init__(self, list, headers = [], parent = None):
        QAbstractTableModel.__init__(self, parent)
        self.list = list
        self.headers = headers

    def rowCount(self, parent):
        return len(self.list)

    def columnCount(self, parent):
        return len(self.list[0])

    def flags(self, index):
        row = index.row()
        column = index.column()
        if column == 0:
            return Qt.ItemIsUserCheckable | Qt.ItemIsEnabled
        else:
            return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable

    def data(self, index, role):

        row = index.row()
        column = index.column()

        if role == Qt.EditRole:
            return self.list[row][column]

        if role == Qt.CheckStateRole and column == 0:

            if self.list[row][column].isChecked():
                return QVariant(Qt.Checked)
            else:
                return QVariant(Qt.Unchecked)

        """  CheckBoxのテキストを表示させたい場合
        #if role == Qt.DisplayRole and column == 0:
            #return self.list[row][column].text()
        """

        if role == Qt.DisplayRole:

            row = index.row()
            column = index.column()
            value = self.list[row][column]

            return value

    def setData(self, index, value, role = Qt.EditRole):
        row = index.row()
        column = index.column()

        if role == Qt.EditRole:
            self.list[row][column] = value
            self.dataChanged.emit(index, index)
            return True

        if role == Qt.CheckStateRole and column == 0:
            self.list[row][column] = QCheckBox('')
            if value == Qt.Checked:
                self.list[row][column].setChecked(True)
            else:
                self.list[row][column].setChecked(False)
            self.dataChanged.emit(index, index)
            return True
        return False

    def headerData(self, section, orientation, role):

        if role == Qt.DisplayRole:

            if orientation == Qt.Horizontal:

                if section < len(self.headers):
                    return self.headers[section]
                else:
                    return "not implemented"
            else:
                return "%d" % (section + 1)


if __name__ == '__main__':

    app = QApplication(sys.argv)
    table = View()
    table.show()
    app.exec_()

スクリーンショット 2016-08-02 23.39.51.png

はじめの部分はこれでも動きます。

View_TableModel.py
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.Qt import *
import sys, csv

"""
class Window (QWidget):
    def __init__ (self):
        QWidget.__init__(self, None)
        self.setWindowTitle('Table')
"""

class View (QWidget):
    def __init__ (self, parent=None):
        super(View, self).__init__()

        #サイズ固定
        self.setFixedSize(850, 500)
           :
           :
13
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
18