从另一个嵌套窗口访问嵌套窗口的最佳方法

发布于 2024-11-16 23:57:53 字数 9175 浏览 2 评论 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

  • 所有子对话框(控制容器)都有:​​
    • 返回父对话框的公共 getters
    • 对其子控件执行某些操作的公共方法(因此子控件被隐藏)
  • 根窗口具有返回窗格的公共 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;
   ...
};

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

我这里的情况类似于这个问题:公共 getter 链 (GetMainWnd().GetRightPane().GetRDlg2( )...) 用于访问所需的嵌套对象。 CLDlg1 知道 CRightPane 和 CRDlg2,这违反了 Demeter 定律

通过将 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

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

编辑:

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

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

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

GUI application has following windows hierarchy:

                   CMainWnd                      <---- main window
     CLeftPane                   CRightPane       <---- left and right panes (views)
CLDlg1       CLDlg2          CRDlg1     CRDlg2   <---- controls container windows (dialogs)
...          ...             ...        ...      <---|
CCtrl1       ...             ...        CCtrl2   <---|- controls
...          ...             ...        ...      <---|

Parent windows are above their children.
Each child window is a protected member of parent wnd class.
Each child window class has a reference/pointer to its parent window.
Panes are custom control placeholders(views).
All controls are standard MFC controls.

Some CCtrl1's event handler needs to change CCtrl2 (e.g. to set its text). What is the best way to achieve this? What is the best way to access a window nested in one branch of window hierarchy from another window, nested in another branch of window hierarchy?

I am posting here two solutions.

Solution 1

  • all children dialogs (control containers) have:
    • public getters which return parent dialog and
    • public methods that perform some action on their children controls (so children controls are hidden)
  • root window has public getters which return panes

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

Case I have here is similar to one described in this question: a chain of public getters (GetMainWnd().GetRightPane().GetRDlg2()...) is used to access desired nested object. CLDlg1 knows about CRightPane and CRDlg2 which violates Law of Demeter.

This situation can be avoided by moving SetCtrl2Text(...) method to upper level in hierarchy which is described in:

Solution 2

In this case CMainWnd contains all necessary methods which perform actions in deeply nested controls.

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

This hides window hierarchy from its clients but this approach:

  • makes CMainWnd class overcrowded with public methods which do all actions on all nested controls; CMainWnd serves like a main switch board for all clients' actions;
  • CMainWnd and each nested dialog have these methods repeated in their public interfaces

Which approach would be preferable? Or, is there any other solution/pattern for this problem?

Solution 3

Yet another solution would be using interface class which contains event handlers for particular event source object. Destination object's class implements this interface and event source and handler are loosely coupled. Is this maybe the way to go? Is this a common practice in GUI?

EDIT:

Solution 4 - Publisher/Subscriber Pattern

In previous solution event source object keeps a reference to event handler but the problem arises if there are multiple event listeners (two or more classes need to be updated on event). Publisher/Subscriber (Observer) Pattern solves this. I made a little research on this pattern and came up with two versions of how to achieve passing event data from source to handler. Code here is based on the second one:

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());
   ...
}

This solution decouples event source (CLDlg1) and handler (CRDlg2) - they don't know about each other.

Considering solutions above and event driven nature of GUI, my original question is evolving to another form: How to send event from one nested window to another one?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

你如我软肋 2024-11-23 23:57:53

引用OP的评论:

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

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

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

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

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

Quote OP's comment:

Yet another solution would be using
interface class which contains event
handlers for particular event source
object. Destination object's class
implements this interface and event
source and handler are loosely
coupled. Is this maybe the way to go?
Is this a common practice in GUI?

I prefer this solution. This is very common in other language/platform (especially Java), but rare in MFC. Not because its bad but because MFC is way too old fashioned and C oriented.

By the way, more MFC oriented solution would be uses Windows messages and the MFC Command Routing mechanism. This can be done very quickly and easily with OnCmdMsg overrides.

Using explicit interfaces (specifically event source and event listeners) would need more time and effort but give out more readable and maintainable source code.

If you are not comfortable with event listeners, Windows message based design would be more promising way to go than Solution 1, 2.

追星践月 2024-11-23 23:57:53

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

或者使所有控件成员公共

更新:我的意思是自定义对话框类具有友元函数而不是控件。

Maybe you can let the controls be public or use friend methods to save the getter, setter writing. This I think should be accepted in an ui class hierarchy because you don't design your custom controls to be reusable, so they shouldn't be encasulated that hard. They are specific to that single window you are designing anyway.

So accessing it from the top of hierarchy might be done like this (and also I think it would be better to keep all the event handlers in one class at the top):

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

And declare GetCtrl2 friend function in the CLeftPane and CDlg1 classes

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

Or alternatively make all control members public

UPDATE: I meant the custom dialog class have the friend function not the control.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文