При реализации паттерна "Наблюдатель" сообщения виджетов QComboBox отправляются наблюдателям в обратном порядке и задваиваются
Есть две радиокнопки, за изменением состояния которых следит класс 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())

