2011-06-23 53 views
0

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层

  • 所有儿童的对话框(控制容器)有:
    • 公共干将其返回父对话框和
    • 公共方法是对子女的控制执行某些操作(所以儿童控件隐藏)
  • root win道指具有返回窗格

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;  
}; 

案例我这里是类似于一个在this问题描述:公共干将链(GetMainWnd().GetRightPane().GetRDlg2()...)使用访问所需的嵌套对象。 CLDlg1知道违反Law of Demeter的CRightPane和CRDlg2。

溶液2

在这种情况下CMainWnd包含执行深度嵌套控制操作的所有必要的方法:

这种情况可以通过移动SetCtrl2Text(...)方法在层次结构上水平,其中所述被避免。

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:

​​

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 - 发布/订阅模式

在以前的解决方案事件源对象保持对事件处理程序的引用,但如果存在多个事件侦听器的出现问题(两个或更多个类需要在事件上更新)。发布者/订户(观察者)模式解决了这个问题。我对这种模式进行了一些研究,并提出了如何实现将事件数据从源传递给处理程序的two versions。这里的代码是基于第二个:

Observer.h

template<class TEvent> 
class CObserver 
{ 
public: 
    virtual void Update(TEvent& e) = 0; 
}; 

通知。ħ

#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的事件驱动性质,我的原始问题正在演变为另一种形式:如何将事件从一个嵌套窗口发送到另一个窗口?

回答

1

报价OP的评论:

另一种解决方案是使用包含事件 处理特定事件源 对象 接口类。目标对象的类 实现此接口,并且事件 源和处理程序耦合的松散地 。这可能是要走的路吗? 这是GUI中的常见做法吗?

我更喜欢这个解决方案。这在其他语言/平台(尤其是Java)中很常见,但在MFC中很少见。不是因为它的糟糕,而是因为MFC过于老式和面向C语言。

顺便说一下,更多面向MFC的解决方案将使用Windows消息和MFC Command Routing机制。使用OnCmdMsg覆盖可以非常快速和轻松地完成此操作。

使用显式接口(特别是事件源和事件侦听器)将需要更多的时间和精力,但会提供更易读和可维护的源代码。

如果你不舒服的事件监听器,基于Windows消息的设计是比较有前途的路要走比方案1,2

+0

我的直觉告诉我,这实际上是唯一正确的做法,但我不确定MFC中是否有一些快捷方式。在MFC中使用自定义消息不是类型安全的(从WPARAM/LPARAM中转换),我宁愿将事件与Windows中的事件分开,最好使用通用方法 - 我将其添加为解决方案4. –

+0

将您的答案标记为接受使用事件监听器,以保持类的解耦(我选择的路线来解决这个问题)。谢谢! –

0

也许你可以让控件公开或使用好友的方法来保存getter,setter的书写。我认为这应该在UI类层次结构中被接受,因为您不会将您的自定义控件设计为可重用的,所以它们不应该难以安置。它们特定于您正在设计的单个窗口。

所以从层次结构的顶部访问可能会是这样做(而且我认为这将是更好地让所有的事件处理程序中的一个类别在顶部):

class CMainWnd 
{ 
private: 
    CCtrl1 GetCtrl1(); 
    CCtrl2 GetCtrl2() 
    { 
     return m_leftPane.m_lDlg1.m_ctrl2; 
    } 
} 

,并宣布GetCtrl2在CLeftPane和CDlg1类友元函数

class CDlg1 
{ 
    friend CCtrl2 CMainWnd::GetCtrl2(); 
} 

或可选择地使所有控制元件的公共

UPDATE:我的意思是自定义对话框类的朋友功能不是控件。

+0

我写道:“所有的控制都是标准的MFC的控制。”所以不能改变他们的代码,并认为这不会回答这个问题。我的困境涵盖了控件容器的设计 - 从对话类开始。我更喜欢保持控件的保护,因为对话的客户不应该知道它们。 –

+0

是的,我在那里犯了一个错误。容器类应该具有主窗口的好友功能,以获取属于该容器的每个控件,并且它是子容器 – sekmet64

+0

那么您是否问过如何从不同的地方访问容器的控件并在链中编写getter/setter为此或做其他事情。我想最好是将控件设置为受保护的,但我所说的是控件层次结构基本上是一个单一的代码单元,那么为什么要将所有内容封装到容器类中,如果要从事件处理程序访问它们呢?通过使用朋友函数,您不必使用公共成员,但仍然可以通过单个getter函数访问它在容器链中需要的所有内容。 – sekmet64