PyQt 5信号によるMVCの実現例


周知のようにMVCはいいものです。先日、インターネットで検索しましたが、PyQt 5を使ってMVCを実現する中国語文書については不足しています。良質な文書は一枚しか見つけられませんでした。このようにして、来て、穴を開けて、新しい知識を学んで、流量を引きつけます。ところで、PyQt 5については、レイアウトをよく見てください。容器類コントロールはよく見てください。マルチスレッドと自動化テストがあります。しかし、完璧なGUIを書くには多くのコード経験とドキュメントクエリの能力が必要です。それから、この部分は穴を埋めました。
本題に戻ります。もしこの時直面するシーンがあれば、一つのソフトはいくつかのページに関連しています。各ページは個別のコードです。各ページは自分のコントロールが必要で、最終的にはすべてのコントロールをまとめて、統一的に管理します。
本文の中で、文字はただ補助的に理解して、必ずコードを読みます。
シグナル
GUIでは、コントロールの状態が変化すると、他のコントロールに通知する必要があります。つまり、オブジェクト間の通信が可能になります。イベントが発生したり状態が変化したりすると、信号がこのイベントに関連する関数をトリガします。私たちのこの関数はスロットです。信号と溝は、複数対の複数の関係であってもよい。クラス作成時に定義される信号は、初期化の前に定義される必要があります。
カスタム信号と溝
質問しないでください。以下のコードを静かに感じてください。以下のコードには、信号の定義、指定パラメータの種類、送信、バインディングスロット関数など一連のプロセスが含まれています。

from PyQt5.QtCore import QObject, pyqtSignal

#     
class QSignal(QObject):
  #     
  #        ,                
  #            ,   
  send_msg = pyqtSignal(str, str)

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

  def run(self):
    #     
    self.send_msg.emit('First arg', 'Second arg')

#    
class QSlot(QObject):
  def __init__(self):
    super(QSlot, self).__init__()

  def get(self, *args):
    #     
    print("Get message =>" + args[0], args[1], sep=', ')

if __name__ == '__main__':
  send = QSignal()
  slot = QSlot()

  #          
  send.send_msg.connect(slot.get)
  #          
  send.run()
  #         
  send.send_msg.disconnect(slot.get)
  send.run()
信号バインディングのカスタム溝を内蔵
このようにして、ウィンドウと結合した例をもう一つ見てみましょう。ウィンドウにボタンがあります。ボタンを押すと、ウィンドウを終了します。この例は簡単ですが、信号や溝を使わずに実現できます。しかし、ここでは例をあげて静心的に感じています。信号接続、送信、受信の全論理です。

import sys
from functools import partial
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import (QMainWindow, QApplication, QPushButton, QWidget, 
               QHBoxLayout)


class MainWindow(QMainWindow):
  btn_signal = pyqtSignal()
  def __init__(self):
    super(MainWindow, self).__init__()

    a = QPushButton("  ")
    #              
    a.clicked.connect(partial(self.btn_clicked, 1))
    self.btn_signal.connect(self.close)

    self.setWindowTitle("  ")

    main_widget = QWidget()
    layout = QHBoxLayout()
    layout.addWidget(a)
    main_widget.setLayout(layout)
    # QMainWindow       
    self.setCentralWidget(main_widget)

  def btn_clicked(self, n):
    print(n)
    self.btn_signal.emit()

  def close(self):
    app = QApplication.instance()
    app.quit()


if __name__ == "__main__":
  #  shell   
  app = QApplication(sys.argv)
  mywin = MainWindow()
  mywin.show()
  #      ,    
  sys.exit(app.exec())
ここで、バインディングの溝関数btn_をあげたいです。clickedは追加のパラメータを転送しますが、信号結合時に追加のパラメータを追加することはできません。上記の例に対応して、close()は、信号のパラメータとタイプを指定することによりパラメータを増加することができるが、btn_clicked()はできません。一つの解は、関数とパラメータを結びつける万能なpartial関数を取り出すことである。
ここで信号の働き方や原理を知るべきです。信号の詳細については、リロードやデコレーションなど、ここでは詳しくは公式文書を参照してください。ところで、当時の学習方法にも感心しました。今では多くのコントロールの意味と様々なスタイルのコードを忘れてしまいました。APIを調べに行きます。
MVC
MVCの名前はすべて聞いたことがあるべきで、model、viewとcontrol、つまりデータベース、ページと処理のロジックは互いに分離して、このように書くコードは更に専一化します。ここでコードに感銘を与えました。三つの内容は三つの種類で実現されます。個人はこのように書くことを勧めません。三つのフォルダの下にファイルを置くことを勧めます。一つのコードに入れるのではありません。

import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QPushButton, QMessageBox, 
               QLineEdit, QApplication)

# View
class MainWindow(QWidget):
  verifySignal = QtCore.pyqtSignal()

  def __init__(self, *args, **kwargs):
    super(MainWindow, self).__init__(*args, **kwargs)
    self.id_line = QLineEdit()
    self.id_line.setPlaceholderText("     ")
    self.psd_line = QLineEdit()
    self.psd_line.setPlaceholderText("     ")

    self.init()

  def init(self):

    layout = QHBoxLayout()
    self.setLayout(layout)

    self.button = QPushButton("  ")
    layout.addWidget(self.button)

    layout.addWidget(self.id_line)
    layout.addWidget(self.psd_line)
  
    #        
    self.button.clicked.connect(self.verify_emit)

  def verify_emit(self):
    self.verifySignal.emit()

  def verify_ok(self):
    QMessageBox.about(self, "    ", "    ")

  def verify_no(self):
    QMessageBox.about(self, "       ", "       ")

# model
class Student(object):

  def __init__(self):
    self.name = "aaa"
    self.password = "aaa"

# control
class LoginControll(object):

  def __init__(self):
    #            
    self._app = QApplication([])
    self._model = Student()
    self._view = MainWindow()
    self.init()

  def init(self):
    self._view.verifySignal.connect(self.verify_user)

  def verify_user(self):
    id_ = self._view.id_line.text()
    psd_ = self._view.psd_line.text()

    if id_ == self._model.name and psd_ == self._model.password:
      self._view.verify_ok()
    else:
      self._view.verify_no()

  def run(self):
    self._view.show()
    #     ,      
    return self._app.exec_()

# main.py
if __name__ == "__main__":
  login_control_ = LoginControll()
  #      
  sys.exit(login_control_.run())
この例で注意したいのは、model,viewとcontrollerを三つの種類に分けたことです。信号および信号がいつ送信されるかをviewで定義し、信号送信後に接続されるスロット関数、すなわちどのような応答をトリガするかをcontrollerで定義する。このように、信号の送信と接続によって、viewとcontrollerを結びつけた。viewはページの展示と信号の定義を担当して、controllerは信号の接続と機能の実現を担当して、完璧です。
MVC実現
単一ページ
以上の内容を読めば、実戦でも使えるはずです。まず、デモを提供します。上の簡単なMVCの例を3つのファイルに分割します。ここでコードの展示が不便です。私のgithubに足を運んでください。これはファイル構造です。これはマスターファイルです。
複数ページ
複雑なポイントを実現するロジックでは、複数のページ、複数のcontroller、ファイル構造は以下のように示されています。ここに名前をつける時はできるだけ規範化して、ファイル名、類名、関数名、さもなくば自分を酔いやすくします。python main.pyは実行します。

MVC-demo
├─ main.py
├─ UI
│  ├─ leftbtn_ui.py
│  ├─ login_ui.py
│  ├─ main_window_ui.py
│  └─ verify_ui.py
├─ control
│  ├─ controller.py
│  ├─ leftbtn_control.py
│  ├─ login_control.py
│  └─ verify_control.py
└─ model
    └─ model.py
コールの関係は以下の通りです。

ここで注意したいのは変数の生存周期であり、mainがcontrollerを呼び出し、controllerが他のサブコンを呼び出して、一つのクラスを宣言した後で局部変数が消えやすくなり、信号が接続できなくなります。controller.pyでは、典型的なエラーの書き方:

class Controll(object):

  def __init__(self):

    self._app = QApplication([])

    self._stu = Student()

    self._view = MainWindow()
    self.init()

  def init(self):
    #   controller       ,        ,          
    #           ,     
    login_controller = login_control.Controller(self._view, self._stu.name, self._stu.password)
コードファイルが多すぎて、混乱しています。ここで展示しないと、読者が混乱しやすいです。ここでは効果を示すだけで、完全なコードは私のgithubを参照してください。例を見たら、何でも分かります。

以上はPyQt 5が信号でMVCを実現する例の詳細です。PyQt 5が信号でMVCを実現することについて、他の関連記事に注目してください。