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(たとえば、テキストを設定するため)。これを達成するための最良の方法は何ですか?ウィンドウ階層の別のブランチにネストされた別のウィンドウから、ウィンドウ階層のあるブランチにネストされたウィンドウにアクセスするための最良の方法は何ですか?

ここに2つの解決策を投稿します。

解決策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

さらに別の解決策は、特定のイベントソースオブジェクトのイベントハンドラーを含むインターフェイスクラスを使用することです。宛先オブジェクトのクラスはこのインターフェースを実装し、イベントのソースとハンドラーは緩く結合されています。これは多分行く方法ですか?これはGUIの一般的な方法ですか?

編集:

解決策4-パブリッシャー/サブスクライバーパターン

以前のソリューションでは、イベントソースオブジェクトはイベントハンドラーへの参照を保持しますが、複数のイベントリスナーがある場合に問題が発生します(2つ以上のクラスをイベントで更新する必要があります)。パブリッシャー/サブスクライバー(オブザーバー)パターンはこれを解決します。このパターンについて少し調べて、ソースからハンドラーにイベントデータを渡す方法の2つのバージョンを考え出しました。ここのコードは2番目のものに基づいています:

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のイベント駆動型の性質を考慮すると、私の元の質問は別の形式に進化しています。ネストされたウィンドウから別のウィンドウにイベントを送信するにはどうすればよいですか。

4

2 に答える 2

1

OPのコメントを引用:

さらに別の解決策は、特定のイベントソースオブジェクトのイベントハンドラーを含むインターフェイスクラスを使用することです。宛先オブジェクトのクラスはこのインターフェースを実装し、イベントのソースとハンドラーは緩く結合されています。これは多分行く方法ですか?これはGUIの一般的な方法ですか?

私はこの解決策を好みます。これは他の言語/プラットフォーム(特にJava)では非常に一般的ですが、MFCではまれです。悪いからではなく、MFCが古すぎてC指向だからです。

ちなみに、よりMFC指向のソリューションは、WindowsメッセージとMFCコマンドルーティングメカニズムを使用することです。これは、OnCmdMsgオーバーライドを使用して非常に迅速かつ簡単に実行できます。

明示的なインターフェイス(特にイベントソースとイベントリスナー)を使用すると、より多くの時間と労力が必要になりますが、より読みやすく保守しやすいソースコードが提供されます。

イベントリスナーに慣れていない場合は、Windowsメッセージベースの設計がソリューション1、2よりも有望な方法です。

于 2011-06-24T06:31:50.540 に答える
0

たぶん、コントロールを公開したり、フレンドメソッドを使用してゲッターやセッターの書き込みを保存したりできます。これは、カスタムコントロールを再利用できるように設計していないため、UIクラス階層で受け入れる必要があると思います。したがって、それほどハードにカプセル化するべきではありません。それらは、とにかく設計しているその単一のウィンドウに固有です。

したがって、階層の最上位からアクセスするには、次のように行うことができます(また、すべてのイベントハンドラーを最上位の1つのクラスに保持する方がよいと思います)。

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 に答える