2015년 10월 15일 목요일

PySide에서 QDataWidgetMapper,QComboBox 매핑하기

PySide에서 MVVM 패턴으로 GUI를 작성하려면 뷰와 뷰모델간의 상호 데이터 전파가 자동으로 전달되어서 서로 일치하도록 하는게 일이다
OOOview와 OOOmodel의 개념으로 지원되는 위젯들은 데이터 전파가 잘되나, 일반 위젯은 뷰-모델의 개념이 없기 때문에 유사한 흉내를 내주기 위해서는 모델을 하나 만들고QDataWidgetMapper로 각각의 위젯과 연결을 해줌으로써 MVVM 패턴을 적용할 수 있을것으로 생각된다.

LineEdit, DateTime, TextEdit 은 매핑한 후 뷰와 모델간 데이터 변경 전달이 자동으로 잘되지만,
ComboBox의 경우 QDataWidgetMapper에서 currentIndex property로 매핑한 경우 상호간 변경내용 전파가 잘되나, currentText로 매핑할 경우 뷰->모델로는 전파가 되나, 모델->뷰로는 전파가 잘 되지 않는다.

이 문제를 해결하기위해 QComboBox를 상속하여 MyComboBox를 만들었다

테스트 화면을 보면 뷰-뷰모델간 동기화를 위한 별도의 코드없이 매핑만으로 같은 모델을 공유하는 두개의 윈도우간 데이터의 변경결과가 자동 반영되는 것을 알 수 있다.




  • MyComboBox.py


#-*- coding: utf-8 -*-

from PySide.QtGui import QComboBox
from PySide.QtCore import Property, Qt

class MyComboBox(QComboBox):

    def __init__(self, params=None):
        super().__init__(params)


    def set(self, text):
        ''' 텍스트 값을 설정하면 현재 인덱스 값을 텍스트와 일치하는 인덱스로 변경
        '''
        self.setCurrentIndex(self.findText(text, Qt.MatchExactly))

     
    def get(self):
        return self.currentText()

    ''' get을 읽기, set을 쓰기 property로 등록
    '''
    text = Property(str, get, set)

테스트를 위해 회원 등록하는 간단한 GUI의 뷰와 뷰모델은 다음과 같다


  • MemberV.py


import sys
from PySide.QtGui import QWidget, QApplication, QDataWidgetMapper
from Ui_member import Ui_member
from src.mvvmTest.MemberVm import MemberVm

class MemberV(QWidget):
    '''
    classdocs
    '''

    def __init__(self, params=None):
        '''
        Constructor
        '''
        super().__init__()
        self.ui = Ui_member()
        self.ui.setupUi(self)
        self.initCombo()
        self.vm = params
        self.bind()
        self.show()

    def initCombo(self):
        self.ui.comboBoxDept.addItems("월화수목금토일")
        self.ui.comboBoxUsage.addItems("ox")
     
    def bind(self):
        self.mapper = QDataWidgetMapper()
        self.mapper.setModel(self.vm.model)
        self.mapper.addMapping(self.ui.lineEditId, 0)
        self.mapper.addMapping(self.ui.lineEditName, 1)
        self.mapper.addMapping(self.ui.lineEditPwd, 2)
        self.mapper.addMapping(self.ui.lineEditPwd2, 3)
        self.mapper.addMapping(self.ui.comboBoxDept, 4, "text")
        self.mapper.addMapping(self.ui.comboBoxUsage, 5)
        self.mapper.addMapping(self.ui.dateEdit, 6)
        self.mapper.addMapping(self.ui.plainTextEdit, 7)
        self.mapper.toFirst()
     
     
if __name__ == '__main__':
    app = QApplication(sys.argv)

    vm = MemberVm()
    widget = MemberV(vm)
    widget2 = MemberV(vm)
    widget.show()
    widget2.show()
    app.exec_()
    pass


  • MemberVm.py


from PySide.QtGui import QStandardItemModel, QStandardItem

class MemberVm(object):
    '''
    classdocs
    '''


    def __init__(self, params=None):
        '''
        Constructor
        '''
        self.model = QStandardItemModel()

        _id = QStandardItem("")
        name = QStandardItem("")
        pwd = QStandardItem("")
        pwd2 = QStandardItem("")
        dept = QStandardItem("수")
        usage = QStandardItem("o")
        date = QStandardItem("2010-01-01")
        etc = QStandardItem("비고")
        self.model.setItem(0, 0, _id)
        self.model.setItem(0, 1, name)
        self.model.setItem(0, 2, pwd)
        self.model.setItem(0, 3, pwd2)
        self.model.setItem(0, 4, dept)
        self.model.setItem(0, 5, usage)
        self.model.setItem(0, 6, date)
        self.model.setItem(0, 7, etc)

        self.model.itemChanged.connect(self.printModel)

     
    def printModel(self):
        for col in range(7):
            print(self.model.item(0,col).text())


  • Ui_member.py


# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'Ui_member.ui'
#
# Created: Wed Oct 14 00:34:37 2015
#      by: pyside-uic 0.2.15 running on PySide 1.2.2
#
# WARNING! All changes made in this file will be lost!

from PySide import QtCore, QtGui
from PySide.QtGui import QComboBox
from MyComboBox import MyComboBox

class Ui_member(object):
    def setupUi(self, member):
        member.setObjectName("member")
        member.resize(212, 290)
        self.gridLayout = QtGui.QGridLayout(member)
        self.gridLayout.setObjectName("gridLayout")
        self.label = QtGui.QLabel(member)
        self.label.setObjectName("label")
        self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
        self.lineEditId = QtGui.QLineEdit(member)
        self.lineEditId.setObjectName("lineEditId")
        self.gridLayout.addWidget(self.lineEditId, 0, 1, 1, 1)
        self.label_2 = QtGui.QLabel(member)
        self.label_2.setObjectName("label_2")
        self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
        self.lineEditName = QtGui.QLineEdit(member)
        self.lineEditName.setObjectName("lineEditName")
        self.gridLayout.addWidget(self.lineEditName, 1, 1, 1, 1)
        self.label_3 = QtGui.QLabel(member)
        self.label_3.setObjectName("label_3")
        self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
        self.lineEditPwd = QtGui.QLineEdit(member)
        self.lineEditPwd.setObjectName("lineEditPwd")
        self.gridLayout.addWidget(self.lineEditPwd, 2, 1, 1, 1)
        self.label_4 = QtGui.QLabel(member)
        self.label_4.setObjectName("label_4")
        self.gridLayout.addWidget(self.label_4, 3, 0, 1, 1)
        self.lineEditPwd2 = QtGui.QLineEdit(member)
        self.lineEditPwd2.setObjectName("lineEditPwd2")
        self.gridLayout.addWidget(self.lineEditPwd2, 3, 1, 1, 1)
        self.label_5 = QtGui.QLabel(member)
        self.label_5.setObjectName("label_5")
        self.gridLayout.addWidget(self.label_5, 4, 0, 1, 1)
#         self.comboBoxDept = QtGui.QComboBox(member)
        self.comboBoxDept = MyComboBox(member)
        self.comboBoxDept.setObjectName("comboBoxDept")
        self.gridLayout.addWidget(self.comboBoxDept, 4, 1, 1, 1)
        self.label_6 = QtGui.QLabel(member)
        self.label_6.setObjectName("label_6")
        self.gridLayout.addWidget(self.label_6, 5, 0, 1, 1)
        self.comboBoxUsage = QtGui.QComboBox(member)
        self.comboBoxUsage.setObjectName("comboBoxUsage")
        self.gridLayout.addWidget(self.comboBoxUsage, 5, 1, 1, 1)
        self.label_7 = QtGui.QLabel(member)
        self.label_7.setObjectName("label_7")
        self.gridLayout.addWidget(self.label_7, 6, 0, 1, 1)
        self.dateEdit = QtGui.QDateEdit(member)
        self.dateEdit.setObjectName("dateEdit")
        self.gridLayout.addWidget(self.dateEdit, 6, 1, 1, 1)
        self.label_8 = QtGui.QLabel(member)
        self.label_8.setObjectName("label_8")
        self.gridLayout.addWidget(self.label_8, 7, 0, 1, 1)
        self.plainTextEdit = QtGui.QPlainTextEdit(member)
        self.plainTextEdit.setObjectName("plainTextEdit")
        self.gridLayout.addWidget(self.plainTextEdit, 7, 1, 1, 1)

        self.retranslateUi(member)
        QtCore.QMetaObject.connectSlotsByName(member)

    def retranslateUi(self, member):
        member.setWindowTitle(QtGui.QApplication.translate("member", "사용자", None, QtGui.QApplication.UnicodeUTF8))
        self.label.setText(QtGui.QApplication.translate("member", "아이디:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_2.setText(QtGui.QApplication.translate("member", "이름:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_3.setText(QtGui.QApplication.translate("member", "비번:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_4.setText(QtGui.QApplication.translate("member", "비번확인:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_5.setText(QtGui.QApplication.translate("member", "부서:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_6.setText(QtGui.QApplication.translate("member", "사용상태:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_7.setText(QtGui.QApplication.translate("member", "변경일:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_8.setText(QtGui.QApplication.translate("member", "비고:", None, QtGui.QApplication.UnicodeUTF8))

2015년 10월 13일 화요일

PySide QTableView와 QTableModel에서 뷰에서 한 건만 변경해도 여러건이 같이 변경되는 문제

여러가지 테스트를 한 결과,
Postgresql 테이블에 primary key가 없으면 각 행을 구분할 방법이 없기 때문에 데이터 값이 동일한 건은 한건만 수정해도 값이 같은 다른 건들도 같이 업데이트 되는 문제임을 알았다.
알고나니 우습지만, 이 문제 때문에 PySide나 Postgresql의 버그를 의심도 했을만큼 심각하게 고민했었다.
결국 QTableModel이 참조하는 테이블에 유니크 키를 만들어서 문제를 해결했다.

파이썬에서 super() 용도 가장 잘 설명된 자료

http://exynoa.tistory.com/286