ठीक से Pyqt / Qt ऐप्स पर तर्क से UI को कैसे कम करें?


20

मैंने अतीत में इस विषय के बारे में काफी पढ़ा है और अंकल बॉब की इस तरह की कुछ दिलचस्प बातचीत देखी । फिर भी, मुझे अपने डेस्कटॉप एप्लिकेशनों को ठीक से आर्किटेक्ट करने में हमेशा बहुत मुश्किल लगता है और यह भेद करना चाहिए कि यूआई की तरफ जिम्मेदारियां होनी चाहिए और तर्क पक्ष के लोगों की।

अच्छी प्रथाओं का बहुत संक्षिप्त सारांश कुछ इस तरह है। आपको यूआई से डिकोड किए गए अपने तर्क को डिज़ाइन करना चाहिए, ताकि आप अपने पुस्तकालय का उपयोग कर सकें (सैद्धांतिक रूप से) कोई फर्क नहीं पड़ता कि किस तरह का बैकएंड / यूआई फ्रेमवर्क है। इसका मतलब यह है कि मूल रूप से यूआई जितना संभव हो उतना डमी होना चाहिए और तर्क पर भारी प्रसंस्करण किया जाना चाहिए। अन्यथा, मैं सचमुच एक सांत्वना अनुप्रयोग, एक वेब अनुप्रयोग या एक डेस्कटॉप के साथ अपने अच्छे पुस्तकालय का उपयोग कर सकता है।

इसके अलावा, चाचा बॉब सुझाव देते हैं कि किस तकनीक का उपयोग करने की अलग-अलग चर्चाएं आपको बहुत सारे लाभ (अच्छे इंटरफेस) प्रदान करेंगी, डिफरेंसिंग की यह अवधारणा आपको बहुत अच्छी तरह से परीक्षण की गई संस्थाओं को बहुत कम करने की अनुमति देती है, जो बहुत अच्छा लगता है लेकिन फिर भी मुश्किल है।

इसलिए, मुझे पता है कि यह प्रश्न काफी व्यापक प्रश्न है जिस पर संपूर्ण इंटरनेट पर और कई बार अच्छी पुस्तकों के बारे में भी चर्चा हुई है। तो इससे कुछ अच्छा पाने के लिए मैं 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, pytt संकेतों) के लिए युग्मित किया जा रहा है। मुझे पता है कि उदाहरण वास्तव में डमी है और आप इसे एक ही 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
      ...

मेरे एप्लिकेशन में बहुत सारे यूआई तत्व और विजेट हैं, जिन्हें कई प्रोग्रामर द्वारा आसानी से संशोधित करने की आवश्यकता है। "व्यू" कोड में QreeainWidget वाले QMainWindow वाले आइटम होते हैं, जो दाईं ओर एक QStackedWidget द्वारा प्रदर्शित होते हैं (मास्टर-विस्तार देखें)।

क्यूंकि आइटम जोड़े जा सकते हैं और गतिशील रूप से QTreeWidget से हटाए जा सकते हैं, और मैं पूर्ववत करने की कार्यक्षमता का समर्थन करना चाहूंगा, मैंने एक मॉडल बनाने का विकल्प चुना जो वर्तमान / पूर्व राज्यों का ट्रैक रखता है। UI कमांड कंट्रोलर द्वारा मॉडल के माध्यम से जानकारी को जोड़ता है (विजेट को जोड़ना या हटाना, विजेट में जानकारी को अपडेट करना)। कंट्रोलर यूआई तक की जानकारी को केवल सत्यापन, ईवेंट हैंडलिंग, और फ़ाइल / पूर्ववत और फिर से लोड करने पर ही पास करता है।

मॉडल स्वयं UI तत्व आईडी के एक शब्दकोष से बना होता है, जिसका मूल्य उसके पास होता है (और जानकारी के कुछ अतिरिक्त टुकड़े)। मैं पूर्व शब्दकोशों की एक सूची रखता हूं और यदि कोई पूर्ववत हिट करता है तो पिछले एक पर वापस आ सकता है। आखिरकार मॉडल एक निश्चित फ़ाइल प्रारूप के रूप में डिस्क पर डंप हो जाता है।

मैं ईमानदार रहूँगा - मुझे यह डिजाइन करना बहुत कठिन लगा। PyQT ऐसा महसूस नहीं करता है कि यह मॉडल से तलाक लेने के लिए खुद को अच्छी तरह से उधार देता है, और मैं वास्तव में किसी भी ओपन सोर्स प्रोग्राम को ऐसा करने की कोशिश नहीं कर पाया। जिज्ञासु अन्य लोगों ने यह कैसे संपर्क किया।

पुनश्च: मुझे लगता है कि QML MVC करने के लिए एक विकल्प है, और यह आकर्षक लग रहा था जब तक मुझे एहसास नहीं हुआ कि जावास्क्रिप्ट कितना शामिल था - और यह तथ्य अभी भी PyQT (या सिर्फ अवधि) में पोर्ट किए जाने के मामले में काफी अपरिपक्व है। बिना किसी महान डिबगिंग टूल (केवल PyQT के साथ पर्याप्त) और अन्य प्रोग्रामर की आवश्यकता के जटिल कारकों को इस कोड को आसानी से संशोधित करने की आवश्यकता है जो JS को यह नहीं जानते।


0

मैं एक एप्लिकेशन बनाना चाहता था। मैंने व्यक्तिगत कार्यों को लिखना शुरू कर दिया जो छोटे कार्य करता था (डीबी में कुछ के लिए देखो, कुछ की गणना करें, स्वत: पूर्ण के साथ एक उपयोगकर्ता की तलाश करें)। टर्मिनल पर प्रदर्शित। फिर इन तरीकों को एक फाइल में रखें, main.py।।

तब मैं एक यूआई जोड़ना चाहता था। मैंने विभिन्न साधनों के चारों ओर देखा और Qt के लिए बस गया। मैंने UI बनाने के लिए, फिर pyuic4जेनरेट करने के लिए क्रिएटर का इस्तेमाल किया UI.py

में main.py, मैंने आयात किया UI। फिर कोर कार्यक्षमता के शीर्ष पर यूआई घटनाओं द्वारा ट्रिगर होने वाले तरीकों को जोड़ा (शाब्दिक रूप से शीर्ष पर: "कोर" कोड फ़ाइल के निचले भाग में है और UI के साथ कुछ भी नहीं करना है, आप इसे शेल से उपयोग कर सकते हैं यदि आप चाहते हैं सेवा)।

यहां display_suppliersतालिका पर आपूर्तिकर्ताओं (फ़ील्ड: नाम, खाता) की सूची प्रदर्शित करने वाली पद्धति का एक उदाहरण है । (मैं संरचना को स्पष्ट करने के लिए इसे बाकी कोड से काटता हूं)।

जैसा कि उपयोगकर्ता पाठ क्षेत्र HSGsupplierNameEditमें टाइप करता है, पाठ बदलता है और हर बार ऐसा होता है, इस विधि को कहा जाता है ताकि उपयोगकर्ता के प्रकार के रूप में तालिका बदल जाए।

यह आपूर्तिकर्ताओं को एक विधि से प्राप्त करता है जिसे get_suppliers(opchoice)यूआई से स्वतंत्र किया जाता है और कंसोल से भी काम करता है।

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

मुझे इस तरह की सर्वोत्तम प्रथाओं और चीजों के बारे में ज्यादा जानकारी नहीं है, लेकिन यही मेरे लिए समझ में आता है और संयोगवश मेरे लिए यह आसान हो गया है कि मैं एक हाईटस के बाद ऐप में वापस आ जाऊं और वेब 2 खप का उपयोग करके उसमें से एक वेब एप्लिकेशन बनाना चाहता हूं। या webapp2। तथ्य यह है कि वास्तव में सामान करता है कोड स्वतंत्र है और तल पर यह बस इसे हड़पने के लिए आसान बनाता है, और फिर बस परिणाम कैसे प्रदर्शित होते हैं (html तत्व बनाम डेस्कटॉप तत्व)।


0

... बहुत सारी खामियां, और अधिक स्पष्ट रूप से मॉडल को यूआई फ्रेमवर्क (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 से आपके मॉडल को पूरी तरह से अलग कर दिया। आप इसे अब एक अलग मॉड्यूल में भी स्थानांतरित कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.