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