При реализации паттерна "Наблюдатель" сообщения виджетов QComboBox отправляются наблюдателям в обратном порядке и задваиваются

Рейтинг: 2Ответов: 1Опубликовано: 01.05.2023

Есть две радиокнопки, за изменением состояния которых следит класс PageLayout. / За изменением состояния комбобокса ComboBoxPrinter следит другой комбобокс ComboBoxPaperSize.

За изменением ComboBoxPaperSize следит класс PageLayout.
За изменением PageLayout следит класс Scene.
Всё это я реализовал с помощью паттерна «Наблюдатель».

Код такой:

from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
from PyQt6.QtGui import *
import sys

list1 = ["a3", "a4", "a5"]
list2 = ["a1", "a2", "a3", "a4"]
printer = {"Samsung": list1,
            "HP": list2 }

class Func():
        
    ################# методы изменяющие PageLayout #################
    def function_portrait (*args):      
        print(f"function_portrait: виджет {args[1].get_name()}  посылает сигнал {args[0].get_name()}")
        
    def function_landscape(*args):
        print(f"function_landscape: виджет {args[1].get_name()}  посылает сигнал {args[0].get_name()}")
    
    def function_papersize(*args):
        print(f"function_papersize: виджет {args[1].get_name()}  посылает сигнал {args[0].get_name()}")
        
    function_for_pagelayout = {
        "portrait": function_portrait,
        "landscape": function_landscape,
        "papersize": function_papersize     
        }
    
    #### методы на клик по выбору принтера ComboBoxPrinter ###################
    def function_choise_printer(*args):
        args[0].clear()         
        args[0].addItems(printer[args[1].currentText()])        
        print(f"function_choise_printer: виджет {args[1].get_name()}  посылает сигнал {args[0].get_name()}")
        
    function_for_combobox_printer = {
        "printers": function_choise_printer
        }
    
    #### методы измененяющие Scene ##################################
    def function_scene_page_layout(*args):
        print(f"function_scene_page_layout: виджет {args[1].get_name()}  посылает сигнал {args[0].get_name()}")
    
    function_for_scene = {
        "pagelayout": function_scene_page_layout
        }
    
# наблюдатель
class Observer():
    def update_observer():
        pass

#наблюдаемое
class Subject():
    
    #имя 
    def set_name(self, name: str):
        pass
    
    def get_name(self) -> str:
        pass
        
    # сообщение наблюдателю 
    def notify(self):
        pass
    
    # добавление наблюдателей
    def attach(self, observer: Observer):       
        pass 
            
    # удаление наблюдателей
    def detach(self, observer: Observer):
        pass 

class RadioButtonPortraitAndLandscape(QRadioButton, Subject):   
    def __init__(self, name: str, parent=None):
        super().__init__(name)
        self.list_observers = []
        
    def set_name(self, name: str):
        self.__name = name
    
    def get_name(self) -> str:
        return self.__name
            
    def mousePressEvent(self, event):
        self.setChecked(True)
        self.notify()
        
    def notify(self):
        for i in self.list_observers:
            i.update_observer(self)
                
    def attach(self, observer: Observer):       
        self.list_observers.append(observer)
                            
    def detach(self, observer: Observer):
        self.list_observers.remove(observer)
        
class ComboBoxPaperSize(QComboBox, Subject, Observer): 
    def __init__(self, parent=None):
        super().__init__()
        self.addItems(list1)
        self.list_observers = []
        self.currentTextChanged[str].connect(self.notify)
                
    def set_name(self, name: str):
        self.__name = name
    
    def get_name(self) -> str:
        return self.__name
            
    def notify(self):       
        for i in self.list_observers:
            i.update_observer(self)         
                            
    def attach(self, observer: Observer):       
        self.list_observers.append(observer)
                            
    def detach(self, observer: Observer):
        self.list_observers.remove(observer)
        
    def update_observer(self, subject: Subject):        
        Func.function_for_combobox_printer[subject.get_name()](self, subject)
    
class ComboBoxPrinter(QComboBox, Subject):
    def __init__(self, parent=None):
        super().__init__()
        self.addItems([key for key, value in printer.items()])
        self.list_observers = []
        self.currentTextChanged[str].connect(self.notify)
                
    def set_name(self, name: str):
        self.__name = name
    
    def get_name(self) -> str:
        return self.__name      
    
    def notify(self):       
        for i in self.list_observers:
            i.update_observer(self)
                                        
    def attach(self, observer: Observer):       
        self.list_observers.append(observer)
                            
    def detach(self, observer: Observer):
        self.list_observers.remove(observer)
    
class PageLayout(QPageLayout, Observer, Subject):
    def __init__(self):
        super().__init__()
        self.list_observers = []
        
    def set_name(self, name: str):
        self.__name = name
    
    def get_name(self) -> str:
        return self.__name
        
    def update_observer(self, subject: Subject):        
        Func.function_for_pagelayout[subject.get_name()](self, subject)
        self.notify()
                
    def notify(self): 
        for i in self.list_observers:
            i.update_observer(self) 
                
    def attach(self, observer: Observer):       
        self.list_observers.append(observer)
                            
    def detach(self, observer: Observer):
        self.list_observers.remove(observer)

class Scene(QGraphicsScene, Observer):
    def __init__(self):
        super().__init__()
        
    def set_name(self, name: str):
        self.__name = name
    
    def get_name(self) -> str:
        return self.__name
        
    def update_observer(self, subject: Subject):                
        Func.function_for_scene[subject.get_name()](self, subject)
        
##################################################################
app = QApplication(sys.argv)

window = QWidget()
window.setWindowTitle("Print")

scene = Scene()
scene.set_name("scene")

pagelayout = PageLayout()
pagelayout.set_name("pagelayout")
pagelayout.attach(scene)

port = RadioButtonPortraitAndLandscape("portrait")
port.setChecked(True)
port.set_name("portrait")
port.attach(pagelayout)

land = RadioButtonPortraitAndLandscape("landscape")
land.set_name("landscape")
land.attach(pagelayout)

paper = ComboBoxPaperSize()
paper.set_name("papersize")
paper.attach(pagelayout)

prin = ComboBoxPrinter()
prin.set_name("printers")
prin.attach(paper)

layout = QVBoxLayout()
layout.addWidget(port)
layout.addWidget(land)
layout.addWidget(prin)
layout.addWidget(paper)

window.setLayout(layout)

window.show()
app.exec()

Всё работает. Только есть один нюанс в последовательности отправки сообщений от источника к наблюдателям.

При выборе принтера из ComboBoxPrinter почему-то первым посылает сообщения своим наблюдателям не ComboBoxPrinter, а ComboBoxPaperSize, и эти сообщения задваиваются.
Не могу понять, в чём причина?

Ответы

▲ 1Принят

bool QObject::disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)

Отключает сигнал в объекте-отправителе от метода в объекте-получателе.

    ...

    def update_observer(self, subject: Subject): 
        self.currentTextChanged.disconnect()                        # !!! +++ 
        Func.function_for_combobox_printer[subject.get_name()](self, subject)
        self.currentTextChanged[str].connect(self.notify)           # !!! +++ 
      
    ...

import sys
'''
from PyQt6.QtWidgets import *
from PyQt6.QtCore import *
from PyQt6.QtGui import *
'''
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.Qt import *


list1 = ["a3", "a4", "a5"]
list2 = ["a1", "a2", "a3", "a4"]
printer = {
    "Samsung": list1,
    "HP": list2 
}

class Func():
        
    ################# методы изменяющие PageLayout #################
    def function_portrait (*args):      
        print(f"function_portrait         : виджет {args[1].get_name()}  посылает сигнал {args[0].get_name()}")
        
    def function_landscape(*args):
        print(f"function_landscape        : виджет {args[1].get_name()}  посылает сигнал {args[0].get_name()}")
    
    def function_papersize(*args):
        print(f"function_papersize        : виджет {args[1].get_name()}  посылает сигнал {args[0].get_name()}")
        
    function_for_pagelayout = {
        "portrait": function_portrait,
        "landscape": function_landscape,
        "papersize": function_papersize     
        }
    
    #### методы на клик по выбору принтера ComboBoxPrinter ###################
    def function_choise_printer(*args):
        args[0].clear()         
        args[0].addItems(printer[args[1].currentText()])        
        print(f"function_choise_printer   : виджет {args[1].get_name()}  посылает сигнал {args[0].get_name()}")
        
    function_for_combobox_printer = {
        "printers": function_choise_printer
        }
    
    #### методы измененяющие Scene ##################################
    def function_scene_page_layout(*args):
        print(f"function_scene_page_layout: виджет {args[1].get_name()}  посылает сигнал {args[0].get_name()}")
    
    function_for_scene = {
        "pagelayout": function_scene_page_layout
        }
    
# наблюдатель
class Observer():
    def update_observer():
        pass

#наблюдаемое
class Subject():
    #имя 
    def set_name(self, name: str):
        pass
    
    def get_name(self) -> str:
        pass
        
    # сообщение наблюдателю 
    def notify(self):
        pass
    
    # добавление наблюдателей
    def attach(self, observer: Observer):       
        pass 
            
    # удаление наблюдателей
    def detach(self, observer: Observer):
        pass 
        

class RadioButtonPortraitAndLandscape(QRadioButton, Subject):   
    def __init__(self, name: str, parent=None):
        super().__init__(name)
        self.list_observers = []
        
    def set_name(self, name: str):
        self.__name = name
    
    def get_name(self) -> str:
        return self.__name
            
    def mousePressEvent(self, event):
        self.setChecked(True)
        self.notify()
        
    def notify(self):
        for i in self.list_observers:
            i.update_observer(self)
                
    def attach(self, observer: Observer):       
        self.list_observers.append(observer)
                            
    def detach(self, observer: Observer):
        self.list_observers.remove(observer)
        
        
class ComboBoxPaperSize(QComboBox, Subject, Observer): 
    def __init__(self, parent=None):
        super().__init__()
        self.addItems(list1)
        self.list_observers = []
        self.currentTextChanged[str].connect(self.notify)
                
    def set_name(self, name: str):
        self.__name = name
    
    def get_name(self) -> str:
        return self.__name

# -----------------> vvvv <-------------------------------------- vvvv <----
    def notify(self, text):
        print(f'\n\tComboBoxPaperSize - def notify(self, text): `{text}`') # +
    
        for i in self.list_observers:
            i.update_observer(self)         
                            
    def attach(self, observer: Observer):       
        self.list_observers.append(observer)
                            
    def detach(self, observer: Observer):
        self.list_observers.remove(observer)
        
    def update_observer(self, subject: Subject): 
# !!! +++ 
        self.currentTextChanged.disconnect()                        # !!! +++ 
        Func.function_for_combobox_printer[subject.get_name()](self, subject)
        self.currentTextChanged[str].connect(self.notify)           # !!! +++ 
        
    
class ComboBoxPrinter(QComboBox, Subject):
    def __init__(self, parent=None):
        super().__init__()
        self.addItems([key for key, value in printer.items()])
        self.list_observers = []
        self.currentTextChanged[str].connect(self.notify)
                
    def set_name(self, name: str):
        self.__name = name
    
    def get_name(self) -> str:
        return self.__name      

# -----------------> vvvv <----------------------------------- vvvv <----
    def notify(self, text):       
        print(f'\n\tComboBoxPrinter - def notify(self, text): {text}') # +
        
        for i in self.list_observers:
            i.update_observer(self)
                                        
    def attach(self, observer: Observer):       
        self.list_observers.append(observer)
                            
    def detach(self, observer: Observer):
        self.list_observers.remove(observer)
    
    
class PageLayout(QPageLayout, Observer, Subject):
    def __init__(self):
        super().__init__()
        self.list_observers = []
        
    def set_name(self, name: str):
        self.__name = name
    
    def get_name(self) -> str:
        return self.__name
        
    def update_observer(self, subject: Subject): 
        Func.function_for_pagelayout[subject.get_name()](self, subject)
        self.notify()
                
    def notify(self): 
        for i in self.list_observers:
            i.update_observer(self) 
                
    def attach(self, observer: Observer):       
        self.list_observers.append(observer)
                            
    def detach(self, observer: Observer):
        self.list_observers.remove(observer)


class Scene(QGraphicsScene, Observer):
    def __init__(self):
        super().__init__()
        
    def set_name(self, name: str):
        self.__name = name
    
    def get_name(self) -> str:
        return self.__name
        
    def update_observer(self, subject: Subject):          
        Func.function_for_scene[subject.get_name()](self, subject)
        

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

    window = QWidget()
    window.setWindowTitle("Print")

    scene = Scene()
    scene.set_name("scene")

    pagelayout = PageLayout()
    pagelayout.set_name("pagelayout")
    pagelayout.attach(scene)

    port = RadioButtonPortraitAndLandscape("portrait")
    port.setChecked(True)
    port.set_name("portrait")
    port.attach(pagelayout)

    land = RadioButtonPortraitAndLandscape("landscape")
    land.set_name("landscape")
    land.attach(pagelayout)

    paper = ComboBoxPaperSize()
    paper.set_name("papersize")
    paper.attach(pagelayout)

    prin = ComboBoxPrinter()
    prin.set_name("printers")
    prin.attach(paper)

    layout = QVBoxLayout()
    layout.addWidget(port)
    layout.addWidget(land)
    layout.addWidget(prin)
    layout.addWidget(paper)

    window.setLayout(layout)

    window.show()
    sys.exit(app.exec())

введите сюда описание изображения

введите сюда описание изображения