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 控件。

SomeCCtrl1的事件处理程序需要更改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;
   ...
};

右窗格.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;       
};

我在这里的情况与问题中描述的情况类似:公共 getter ( GetMainWnd().GetRightPane().GetRDlg2()...) 链用于访问所需的嵌套对象。CLDlg1 知道违反Demeter 定律的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);
}

右窗格.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

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

编辑:

解决方案 4 - 发布者/订阅者模式

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

观察者.h

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

通知程序.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;
   ...
};

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

4

2 回答 2

1

引用 OP 的评论:

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

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

顺便说一句,更多面向 MFC 的解决方案将使用 Windows 消息和MFC 命令路由机制。这可以通过 OnCmdMsg 覆盖快速轻松地完成。

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

如果您对事件侦听器不满意,那么基于 Windows 消息的设计将比解决方案 1、2 更有希望。

于 2011-06-24T06:31:50.540 回答
0

也许您可以让控件公开或使用友元方法来保存 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();
}

或者将所有控制成员公开

更新:我的意思是自定义对话框类具有朋友功能而不是控件。

于 2011-06-23T15:25:26.730 回答