Pyqt / Qt 앱의 UI와 UI를 올바르게 분리하는 방법은 무엇입니까?


20

나는 과거 에이 주제에 대해 꽤 많이 읽었으며 밥 삼촌의 이와 같은 흥미로운 이야기를 보았습니다 . 그럼에도 불구하고 항상 데스크톱 응용 프로그램을 올바르게 설계하고 UI 쪽 의 책임 과 논리 쪽 의 책임을 구분하기가 매우 어렵습니다 .

모범 사례에 대한 매우 간단한 요약은 다음과 같습니다. 어떤 종류의 백엔드 / UI 프레임 워크에 상관없이 라이브러리를 (이론적으로) 사용할 수 있도록 UI와 분리 된 로직을 설계해야합니다. 이것이 의미하는 바는 기본적으로 UI는 가능한 한 더미가되어야하고 많은 처리는 로직 측면에서 수행되어야합니다. 그렇지 않으면 말 그대로 콘솔 응용 프로그램, 웹 응용 프로그램 또는 데스크톱 응용 프로그램과 함께 멋진 라이브러리를 사용할 수 있습니다.

또한 Bob 삼촌은 어떤 기술을 사용할 지에 대한 많은 토론 (좋은 인터페이스)에 대해 다른 토론을 제안합니다.이 지연 개념을 사용하면 잘 테스트 된 엔티티를 크게 분리 할 수 ​​있지만 여전히 까다 롭습니다.

그래서 저는이 질문이 인터넷 전체와 많은 훌륭한 책들에서 여러 번 논의 된 광범위한 질문이라는 것을 알고 있습니다. 그래서 좋은 결과를 얻으려면 pyqt에서 MCV를 사용하는 더미 예제를 게시 할 것입니다.

import sys
import os
import random

from PyQt5 import QtWidgets
from PyQt5 import QtGui
from PyQt5 import QtCore

random.seed(1)


class Model(QtCore.QObject):

    item_added = QtCore.pyqtSignal(int)
    item_removed = QtCore.pyqtSignal(int)

    def __init__(self):
        super().__init__()
        self.items = {}

    def add_item(self):
        guid = random.randint(0, 10000)
        new_item = {
            "pos": [random.randint(50, 100), random.randint(50, 100)]
        }
        self.items[guid] = new_item
        self.item_added.emit(guid)

    def remove_item(self):
        list_keys = list(self.items.keys())

        if len(list_keys) == 0:
            self.item_removed.emit(-1)
            return

        guid = random.choice(list_keys)
        self.item_removed.emit(guid)
        del self.items[guid]


class View1():

    def __init__(self, main_window):
        self.main_window = main_window

        view = QtWidgets.QGraphicsView()
        self.scene = QtWidgets.QGraphicsScene(None)
        self.scene.addText("Hello, world!")

        view.setScene(self.scene)
        view.setStyleSheet("background-color: red;")

        main_window.setCentralWidget(view)


class View2():

    add_item = QtCore.pyqtSignal(int)
    remove_item = QtCore.pyqtSignal(int)

    def __init__(self, main_window):
        self.main_window = main_window

        button_add = QtWidgets.QPushButton("Add")
        button_remove = QtWidgets.QPushButton("Remove")
        vbl = QtWidgets.QVBoxLayout()
        vbl.addWidget(button_add)
        vbl.addWidget(button_remove)
        view = QtWidgets.QWidget()
        view.setLayout(vbl)

        view_dock = QtWidgets.QDockWidget('View2', main_window)
        view_dock.setWidget(view)

        main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, view_dock)

        model = main_window.model
        button_add.clicked.connect(model.add_item)
        button_remove.clicked.connect(model.remove_item)


class Controller():

    def __init__(self, main_window):
        self.main_window = main_window

    def on_item_added(self, guid):
        view1 = self.main_window.view1
        model = self.main_window.model

        print("item guid={0} added".format(guid))
        item = model.items[guid]
        x, y = item["pos"]
        graphics_item = QtWidgets.QGraphicsEllipseItem(x, y, 60, 40)
        item["graphics_item"] = graphics_item
        view1.scene.addItem(graphics_item)

    def on_item_removed(self, guid):
        if guid < 0:
            print("global cache of items is empty")
        else:
            view1 = self.main_window.view1
            model = self.main_window.model

            item = model.items[guid]
            x, y = item["pos"]
            graphics_item = item["graphics_item"]
            view1.scene.removeItem(graphics_item)
            print("item guid={0} removed".format(guid))


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()

        # (M)odel ===> Model/Library containing should be UI agnostic, right now it's not
        self.model = Model()

        # (V)iew      ===> Coupled to UI
        self.view1 = View1(self)
        self.view2 = View2(self)

        # (C)ontroller ==> Coupled to UI
        self.controller = Controller(self)

        self.attach_views_to_model()

    def attach_views_to_model(self):
        self.model.item_added.connect(self.controller.on_item_added)
        self.model.item_removed.connect(self.controller.on_item_removed)


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

    form = MainWindow()
    form.setMinimumSize(800, 600)
    form.show()
    sys.exit(app.exec_())

위의 스 니펫에는 많은 결함이 포함되어 있으며 모델이 UI 프레임 워크 (QObject, pyqt 신호)에 결합되어있는 것이 더 분명합니다. 예제가 실제로 더미라는 것을 알고 단일 QMainWindow를 사용하여 몇 줄로 코딩 할 수는 있지만 더 큰 pyqt 응용 프로그램을 올바르게 설계하는 방법을 이해하는 것이 목적입니다.

의문

좋은 일반 관행에 따라 MVC를 사용하여 큰 PyQt 응용 프로그램을 어떻게 올바르게 설계 하시겠습니까?

참조

나는 이것과 비슷한 질문을했습니다 .

답변:


1

나는 (주로) WPF / ASP.NET 배경에서 왔으며 지금 MVC-ish PyQT 앱을 만들려고 하는데이 질문은 나를 괴롭 힙니다. 나는 내가하고있는 일을 공유 할 것이며 건설적인 의견이나 비판이 궁금하다.

다음은 약간의 ASCII 다이어그램입니다.

View                          Controller             Model
---------------
| QMainWindow |   ---------> controller.py <----   Dictionary containing:
---------------   Add, remove from View                |
       |                                               |
    QWidget       Restore elements from Model       UIElementId + data
       |                                               |
    QWidget                                         UIElementId + data
       |                                               |
    QWidget                                         UIElementId + data
      ...

내 응용 프로그램에는 많은 프로그래머가 쉽게 수정 해야하는 많은 UI 요소와 위젯이 있습니다. "view"코드는 오른쪽에 QStackedWidget에 의해 표시되는 항목을 포함하는 QTreeWidget이있는 QMainWindow로 구성됩니다 (마스터 세부 사항보기).

QTreeWidget에서 항목을 동적으로 추가 및 제거 할 수 있고 실행 취소 기능을 지원하고 싶습니다. 현재 / 이전 상태를 추적하는 모델을 선택했습니다. UI 명령은 컨트롤러에 의해 모델에 정보를 전달합니다 (위젯 추가 또는 제거, 위젯의 정보 업데이트). 컨트롤러가 정보를 UI에 전달하는 유일한 경우는 유효성 검사, 이벤트 처리 및 파일로드 / 실행 취소 및 다시 실행에 관한 것입니다.

모델 자체는 마지막으로 보유한 값과 몇 가지 추가 정보가 포함 된 UI 요소 ID의 사전으로 구성됩니다. 이전 사전 목록을 유지하고 누군가가 실행 취소를하면 이전 사전으로 되돌릴 수 있습니다. 결국 모델은 특정 파일 형식으로 디스크에 덤프됩니다.

나는 정직 할 것이다-나는 이것을 디자인하는 것이 매우 어렵다는 것을 알았다. PyQT는 모델과 이혼하는 것이 좋지 않은 느낌이 들며, 이와 비슷한 것을하려고하는 오픈 소스 프로그램을 실제로 찾을 수 없었습니다. 다른 사람들이 어떻게 접근했는지 궁금합니다.

추신 : QML이 MVC를 수행하는 옵션이라는 것을 알고 있으며, 얼마나 많은 Javascript가 포함되어 있는지, 그리고 PyQT (또는 단지 기간)로 이식되는 측면에서 여전히 미숙하다는 사실을 알 때까지는 매력적으로 보였습니다. 훌륭한 디버깅 툴 (PyQT만으로는 충분하지 않음)의 복잡한 요소와 JS가 알지 못하는 다른 프로그래머가이 코드를 쉽게 수정해야 할 필요성.


0

응용 프로그램을 만들고 싶었습니다. 작은 작업을 수행하는 개별 함수를 작성하기 시작했습니다 (db에서 무언가를 찾고, 무언가를 계산하고, 자동 완성 기능이있는 사용자를 찾으십시오). 터미널에 표시됩니다. 그런 다음이 메소드를 파일에 넣으십시오 main.py.

그런 다음 UI를 추가하고 싶었습니다. 다른 도구를 둘러보고 Qt에 정착했습니다. Creator를 사용하여 UI를 만든 다음 pyuic4생성했습니다 UI.py.

에서가 main.py, 나 수입 UI. 그런 다음 핵심 기능 위에 UI 이벤트에 의해 트리거되는 메소드를 추가했습니다. (문자 그대로 : "core"코드는 파일의 맨 아래에 있으며 UI와 관련이 없습니다. 에).

다음 display_suppliers은 테이블에 공급 업체 (필드 : 이름, 계정) 목록을 표시하는 방법의 예입니다 . (구조를 설명하기 위해 나머지 코드에서 이것을 잘라 냈습니다).

텍스트 필드에 사용자가 입력 HSGsupplierNameEdit하면 텍스트가 변경되고 변경 될 때마다이 메소드가 호출되므로 사용자 유형에 따라 테이블이 변경됩니다.

get_suppliers(opchoice)UI와 독립적이며 콘솔에서도 작동 하는 메소드에서 공급 업체를 가져옵니다 .

from PyQt4 import QtCore, QtGui
import UI

class Treasury(QtGui.QMainWindow):

    def __init__(self, parent=None):
        self.ui = UI.Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.HSGsuppliersTable.resizeColumnsToContents()
        self.ui.HSGsupplierNameEdit.textChanged.connect(self.display_suppliers)

    @QtCore.pyqtSlot()
    def display_suppliers(self):

        """
            Display list of HSG suppliers in a Table.
        """
        # TODO: Refactor this code and make it generic
        #       to display a list on chosen Table.


        self.suppliers_virement = self.get_suppliers(self.OP_VIREMENT)
        name = unicode(self.ui.HSGsupplierNameEdit.text(), 'utf_8')
        # Small hack for auto-modifying list.
        filtered = [sup for sup in self.suppliers_virement if name.upper() in sup[0]]

        row_count = len(filtered)
        self.ui.HSGsuppliersTable.setRowCount(row_count)

        # supplier[0] is the supplier's name.
        # supplier[1] is the supplier's account number.

        for index, supplier in enumerate(filtered):
            self.ui.HSGsuppliersTable.setItem(
                index,
                0,
                QtGui.QTableWidgetItem(supplier[0])
            )

            self.ui.HSGsuppliersTable.setItem(
                index,
                1,
                QtGui.QTableWidgetItem(self.get_supplier_bank(supplier[1]))
            )

            self.ui.HSGsuppliersTable.setItem(
                index,
                2,
                QtGui.QTableWidgetItem(supplier[1])
            )

            self.ui.HSGsuppliersTable.resizeColumnsToContents()
            self.ui.HSGsuppliersTable.horizontalHeader().setStretchLastSection(True)


    def get_suppliers(self, opchoice):
        '''
            Return a list of suppliers who are 
            relevant to the chosen operation. 

        '''
        db, cur = self.init_db(SUPPLIERS_DB)
        cur.execute('SELECT * FROM suppliers WHERE operation = ?', (opchoice,))
        data = cur.fetchall()
        db.close()
        return data

나는 모범 사례와 그와 같은 것들에 대해 많이 알지 못하지만 이것은 나에게 의미가 있으며 우연히 중단 후 앱으로 돌아 오기 쉽고 web2py를 사용하여 웹 응용 프로그램을 만들고 싶어합니다. 또는 webapp2. 실제로 작업을 수행하는 코드는 독립적이며 맨 아래에는 코드를 쉽게 잡고 결과가 표시되는 방식 (html 요소 대 데스크톱 요소)을 쉽게 변경할 수 있습니다.


0

... 많은 결함, UI 프레임 워크 (QObject, pyqt 신호)에 연결된 모델이 더 분명합니다.

그러지 마!

class Model(object):
    def __init__(self):
        self.items = {}
        self.add_callbacks = []
        self.del_callbacks = []

    # just use regular callbacks, caller can provide a lambda or whatever
    # to make the desired Qt call
    def emit_add(self, guid):
        for cb in self.add_callbacks:
            cb(guid)

Qt에서 모델을 완전히 분리 한 사소한 변경이었습니다. 이제 다른 모듈로 옮길 수도 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.