Лучший способ получить доступ к вложенному окну из другого вложенного окна - PullRequest
0 голосов
/ 23 июня 2011

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, мой оригинальныйвопрос развивается в другую форму: Как отправить событие из одного вложенного окна в другое?

Ответы [ 2 ]

1 голос
/ 24 июня 2011

Цитировать комментарий ОП:

Еще одним решением было бы использование класса интерфейса, который содержит обработчики событий для конкретного объекта источника события.Класс целевого объекта реализует этот интерфейс, а источник и обработчик события слабо связаны.Может быть, это путь?Это обычная практика в GUI?

Я предпочитаю это решение.Это очень распространено в других языках / платформах (особенно в Java), но редко встречается в MFC.Не потому, что это плохо, а потому, что MFC слишком старомоден и ориентирован на C.

Кстати, более ориентированное на MFC решение будет использовать сообщения Windows и механизм MFC Command Routing .Это можно сделать очень быстро и легко с помощью переопределений OnCmdMsg.

Использование явных интерфейсов (в частности, источника событий и прослушивателей событий) потребует больше времени и усилий, но выдает более читаемый и поддерживаемый исходный код.* Если вас не устраивают прослушиватели событий, дизайн на основе сообщений Windows будет более перспективным, чем решение 1, 2.

0 голосов
/ 23 июня 2011

Может быть, вы можете позволить элементам управления быть публичными или использовать дружественные методы для сохранения записи метода получения и установки.Это, я думаю, должно быть принято в иерархии классов пользовательского интерфейса, потому что вы не разрабатываете свои пользовательские элементы управления для повторного использования, поэтому их не следует заключать так сложно.Они специфичны для того единственного окна, которое вы разрабатываете в любом случае.

Так что доступ к нему из верхней части иерархии может быть сделан следующим образом (а также я думаю, что было бы лучше хранить все обработчики событий в одном классе ввверху):

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

И объявить функцию-дружбу GetCtrl2 в классах CLeftPane и CDlg1

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

Или сделать общедоступными все элементы управления

ОБНОВЛЕНИЕ: Я имел в виду, что в пользовательском классе диалога есть функция друга, а не элемент управления.

...