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
- 所有子对话框(控件容器(都具有:
- 返回父对话框和
- 执行某些操作的公共方法(因此子控件处于隐藏状态(
- 返回父对话框和
- 根窗口具有返回窗格
的公共 getter
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;
...
};
左窗格.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;
...
};
主文.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;
...
};
右窗格.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
另一种解决方案是使用接口类,其中包含特定事件源对象的事件处理程序。目标对象的类实现此接口,事件源和处理程序松散耦合。这也许是要走的路吗?这是 GUI 中的常见做法吗?
编辑:
解决方案 4 - 发布者/订阅者模式
在前面的解决方案中,事件源对象保留对事件处理程序的引用,但如果有多个事件侦听器(需要更新两个或多个类,则会出现问题。发布者/订阅者(观察者(模式解决了这个问题。我对这种模式进行了一些研究,并提出了两个版本的如何实现将事件数据从源传递到处理程序。这里的代码基于第二个:
观察者.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);
}
左窗格.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();
...
};
主文.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的事件驱动性质,我最初的问题正在演变为另一种形式:如何将事件从一个嵌套窗口发送到另一个嵌套窗口?
引用OP的评论:
另一种解决方案是使用 包含事件的接口类 特定事件源的处理程序 对象。目标对象的类 实现此接口和事件 源和处理程序是松散的 耦合。这也许是要走的路吗? 这是 GUI 中的常见做法吗?
我更喜欢这个解决方案。这在其他语言/平台(尤其是Java(中很常见,但在MFC中很少见。不是因为它不好,而是因为MFC太老式和面向C。
顺便说一下,更面向MFC的解决方案将使用Windows消息和MFC命令路由机制。这可以通过 OnCmdMsg 替代非常快速轻松地完成。
使用显式接口(特别是事件源和事件侦听器(需要更多的时间和精力,但会提供更具可读性和可维护性的源代码。
如果您对事件侦听器不满意,则基于 Windows 消息的设计将比解决方案 1、2 更有希望。
也许你可以让控件公开,或者使用朋友方法来保存getter,setter写作。我认为这应该在 ui 类层次结构中被接受,因为您没有将自定义控件设计为可重用的,因此它们不应该那么难隔离。无论如何,它们都特定于您正在设计的单个窗口。
因此,从层次结构的顶部访问它可能是这样完成的(而且我认为最好将所有事件处理程序保存在顶部的一个类中(:
class CMainWnd
{
private:
CCtrl1 GetCtrl1();
CCtrl2 GetCtrl2()
{
return m_leftPane.m_lDlg1.m_ctrl2;
}
}
并在 CLeftPane 和 CDlg1 类中声明 GetCtrl2 友元函数
class CDlg1
{
friend CCtrl2 CMainWnd::GetCtrl2();
}
或者将所有控制成员公开
更新:我的意思是自定义对话框类具有友元函数而不是控件。