GUI-приложение имеет следующую иерархию окон:
CMainWnd <---- main window
CLeftPane CRightPane <---- left and right panes (views)
CLDlg1 CLDlg2 CRDlg1 CRDlg2 <---- controls container windows (dialogs)
... ... ... ... <---|
CCtrl1 ... ... CCtrl2 <---|- controls
... ... ... ... <---|
Родительские окна находятся выше своих дочерних элементов.Каждое дочернее окно является защищенным членом родительского класса wnd.Каждый класс дочернего окна имеет ссылку / указатель на свое родительское окно.Панели - это пользовательские элементы управления заполнением (представления).Все элементы управления являются стандартными элементами управления MFC.
Некоторые обработчики событий CCtrl1
должны изменить CCtrl2
(например, чтобы установить его текст).Каков наилучший способ достичь этого? Каков наилучший способ получить доступ к окну, вложенному в одну ветвь иерархии окон из другого окна, вложенному в другую ветвь иерархии окон?
Я публикую здесь два решения.
Решение 1
- все дочерние диалоги (контейнеры управления) имеют:
- публичные геттеры, которые возвращают родительский диалог и
- публичные методы, которые выполняют некоторые действия с дочерними элементами управления (поэтому дочерние элементы управления скрыты)
- В корневом окне есть публичные геттеры, которые возвращают панели
MainWnd.h:
#include "LeftPane.h"
#include "RightPane.h"
class CMainWnd
{
public:
CLeftPane& GetLeftPane(){return m_leftPane;}
CRightPane& GetRightPane(){return m_rightPane;}
...
protected:
CLeftPane m_leftPane;
CRightPane m_rightPane;
...
};
LeftPane.h:
#include "MainWnd.h"
#include "LDlg1.h"
#include "LDlg2.h"
class CLeftPane
{
public:
CLeftPane(CMainWnd& mainWnd) : m_mainWnd(mainWnd){};
CMainWnd& GetMainWnd() {return m_mainWnd;}
...
protected:
CMainWnd& m_mainWnd;
CLDlg1 m_LDlg1;
CLDlg2 m_LDlg2;
...
};
RightPane.h:
#include "MainWnd.h"
#include "RDlg1.h"
#include "RDlg2.h"
class CRightPane
{
public:
CRightPane(CMainWnd& mainWnd) : m_mainWnd(mainWnd){};
CMainWnd& GetMainWnd() {return m_mainWnd;}
CRDlg2& GetRDlg2() {return m_RDlg2;}
...
protected:
CMainWnd& m_mainWnd;
CRDlg1 m_RDlg1;
CRDlg2 m_RDlg2;
...
};
LDlg1.h:
#include "LeftPane.h"
#include "Ctrl1.h"
class CLDlg1
{
public:
CLDlg1(CLeftPane& leftPane) : m_leftPane(leftPane){}
protected:
CLeftPane& m_leftPane;
CCtrl1 m_ctrl1;
void OnCtrl1Event();
};
LDlg1.cpp:
#include "LDlg1.h"
#include "RDlg2.h"
void CLDlg1::OnCtrl1Event()
{
...
CString strText("test");
m_leftPane.GetMainWnd().GetRightPane().GetRDlg2().SetCtrl2Text(strText);
....
}
RDlg2.h:
#include "RightPane.h"
#include "Ctrl2.h"
class CRDlg2
{
public:
CRDlg2(CRightPane& rightPane) : m_rightPane(rightPane){}
void SetCtrl2Text(const CString& strText) {m_ctrl2.SetWindowText(strText);}
protected:
CRightPane& m_rightPane;
CCtrl2 m_ctrl2;
};
У меня здесь есть случай, аналогичный описанному в этот вопрос: цепочка открытых геттеров (GetMainWnd().GetRightPane().GetRDlg2()...
) используется для доступа к нужному вложенному объекту.CLDlg1 знает о CRightPane и CRDlg2, который нарушает Закон Деметры .
Этой ситуации можно избежать, переместив метод SetCtrl2Text(...)
на верхний уровень в иерархии, который описан в:
Решение 2
В этом случае CMainWnd
содержит все необходимоеметоды, которые выполняют действия в глубоко вложенных элементах управления.
MainWnd.h:
#include "LeftPane.h"
#include "RightPane.h"
class CMainWnd
{
public:
void SetCtrl2Text(const CString& strText);
...
protected:
CLeftPane m_leftPane;
CRightPane m_rightPane;
...
};
MainWnd.cpp:
void CMainWnd::SetCtrl2Text(const CString& strText)
{
m_rightPane.SetCtrl2Text(strText);
}
RightPane.h:
#include "MainWnd.h"
#include "RDlg1.h"
#include "RDlg2.h"
class CRightPane
{
public:
CRightPane(CMainWnd& mainWnd) : m_mainWnd(mainWnd){};
CMainWnd& GetMainWnd() {return m_mainWnd;}
void SetCtrl2Text(const CString& strText);
...
protected:
CMainWnd& m_mainWnd;
CRDlg1 m_RDlg1;
CRDlg2 m_RDlg2;
...
};
RightPane.cpp:
void CRightPane::SetCtrl2Text(const CString& strText)
{
m_RDlg2.SetCtrl2Text(strText);
}
LDlg1.cpp:
#include "LDlg1.h"
void CLDlg1::OnCtrl1Event()
{
...
CString strText("test");
m_leftPane.GetMainWnd().SetCtrl2Text(strText);
....
}
RDlg2.h:
#include "RightPane.h"
#include "Ctrl2.h"
class CRDlg2
{
public:
CRDlg2(CRightPane& rightPane) : m_rightPane(rightPane){}
void SetCtrl2Text(const CString& strText);
protected:
CRightPane& m_rightPane;
CCtrl2 m_ctrl2;
};
RDlg2.cpp:
void CRDlg2::SetCtrl2Text(const CString& strText)
{
m_ctrl2.SetWindowText(strText);
}
Это окно скрываетсяиерархия от своих клиентов, но этот подход:
- делает класс
CMainWnd
переполненным открытыми методами, которые выполняют все действия на всех вложенных элементах управления;CMainWnd
служит основной платой переключателей для всех действий клиентов; - CMainWnd и каждый вложенный диалог повторяют эти методы в своих общедоступных интерфейсах
Какой подход будет предпочтительнее?Или есть ли другое решение / шаблон для этой проблемы?
Решение 3
Еще одним решением было бы использование класса интерфейса, который содержит обработчики событий для конкретного объекта источника события.Класс целевого объекта реализует этот интерфейс, а источник и обработчик события слабо связаны.Может быть, это путь?Является ли это обычной практикой в графическом интерфейсе?
РЕДАКТИРОВАТЬ:
Решение 4 - Шаблон издателя / подписчика
В предыдущем решении исходный объект события сохраняет ссылку на обработчик события, но проблемавозникает, если имеется несколько прослушивателей событий (два или более классов должны быть обновлены по событию).Шаблон Publisher / Subscriber (Observer) решает эту проблему.Я провел небольшое исследование этого паттерна и придумал две версии того, как добиться передачи данных о событиях из источника в обработчик.Код здесь основан на втором:
Observer.h
template<class TEvent>
class CObserver
{
public:
virtual void Update(TEvent& e) = 0;
};
Notifier.h
#include "Observer.h"
#include <set>
template<class TEvent>
class CNotifier
{
std::set<CObserver<TEvent>*> m_observers;
public:
void RegisterObserver(const CObserver<TEvent>& observer)
{
m_observers.insert(const_cast<CObserver<TEvent>*>(&observer));
}
void UnregisterObserver(const CObserver<TEvent>& observer)
{
m_observers.erase(const_cast<CObserver<TEvent>*>(&observer));
}
void Notify(TEvent& e)
{
std::set<CObserver<TEvent>*>::iterator it;
for(it = m_observers.begin(); it != m_observers.end(); it++)
{
(*it)->Update(e);
}
}
};
EventTextChanged.h
class CEventTextChanged
{
CString m_strText;
public:
CEventTextChanged(const CString& strText) : m_strText(strText){}
CString& GetText(){return m_strText;}
};
LDlg1.h:
class CLDlg1
{
CNotifier<CEventTextChanged> m_notifierEventTextChanged;
public:
CNotifier<CEventTextChanged>& GetNotifierEventTextChanged()
{
return m_notifierEventTextChanged;
}
};
LDlg1.cpp:
// CEventTextChanged event source
void CLDlg1::OnCtrl1Event()
{
...
CString strNewText("test");
CEventTextChanged e(strNewText);
m_notifierEventTextChanged.Notify(e);
...
}
RDlg2.h:
class CRDlg2
{
// use inner class to avoid multiple inheritance (in case when this class wants to observe multiple events)
class CObserverEventTextChanged : public CObserver<CEventTextChanged>
{
CActualObserver& m_actualObserver;
public:
CObserverEventTextChanged(CActualObserver& actualObserver) : m_actualObserver(actualObserver){}
void Update(CEventTextChanged& e)
{
m_actualObserver.SetCtrl2Text(e.GetText());
}
} m_observerEventTextChanged;
public:
CObserverEventTextChanged& GetObserverEventTextChanged()
{
return m_observerEventTextChanged;
}
void SetCtrl2Text(const CString& strText);
};
RDlg2.cpp:
void CRDlg2::SetCtrl2Text(const CString& strText)
{
m_ctrl2.SetWindowText(strText);
}
LeftPane.h:
#include "LDlg1.h"
#include "LDlg2.h"
// forward declaration
class CMainWnd;
class CLeftPane
{
friend class CMainWnd;
...
protected:
CLDlg1 m_LDlg1;
CLDlg2 m_LDlg2;
...
};
RightPane.h:
#include "RDlg1.h"
#include "RDlg2.h"
// forward declaration
class CMainWnd;
class CRightPane
{
friend class CMainWnd;
protected:
CRDlg1 m_RDlg1;
CRDlg2 m_RDlg2;
...
};
MainWnd.h:
class CMainWnd
{
...
protected:
CLeftPane m_leftPane;
CRightPane m_rightPane;
...
void Init();
...
};
MainWnd.cpp:
// called after all child windows/dialogs had been created
void CMainWnd::Init()
{
...
// link event source and listener
m_leftPane.m_LDlg1.GetNotifierEventTextChanged().RegisterObserver(m_rightPane.m_RDlg2.GetObserverEventTextChanged());
...
}
Это решение разделяет источник события (CLDlg1
) и обработчик (CRDlg2
) - они не знают друг о друге.
Учитывая вышеизложенные решения и управляемый событиями характер GUI, мой оригинальныйвопрос развивается в другую форму: Как отправить событие из одного вложенного окна в другое?