2011-06-23 56 views
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層

  • 所有兒童的對話框(控制容器)有:
    • 公共干將其返回父對話框和
    • 公共方法是對子女的控制執行某些操作(所以兒童控件隱藏)
  • root win道指具有返回窗格

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

案例我這裏是類似於一個在this問題描述:公共干將鏈(GetMainWnd().GetRightPane().GetRDlg2()...)使用訪問所需的嵌套對象。 CLDlg1知道違反Law of Demeter的CRightPane和CRDlg2。

溶液2

在這種情況下CMainWnd包含執行深度嵌套控制操作的所有必要的方法:

這種情況可以通過移動SetCtrl2Text(...)方法在層次結構上水平,其中所述被避免。

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:

​​

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 - 發佈/訂閱模式

在以前的解決方案事件源對象保持對事件處理程序的引用,但如果存在多個事件偵聽器的出現問題(兩個或更多個類需要在事件上更新)。發佈者/訂戶(觀察者)模式解決了這個問題。我對這種模式進行了一些研究,並提出瞭如何實現將事件數據從源傳遞給處理程序的two versions。這裏的代碼是基於第二個:

Observer.h

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

通知。ħ

#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的事件驅動性質,我的原始問題正在演變爲另一種形式:如何將事件從一個嵌套窗口發送到另一個窗口?

回答

1

報價OP的評論:

另一種解決方案是使用包含事件 處理特定事件源 對象 接口類。目標對象的類 實現此接口,並且事件 源和處理程序耦合的鬆散地 。這可能是要走的路嗎? 這是GUI中的常見做法嗎?

我更喜歡這個解決方案。這在其他語言/平臺(尤其是Java)中很常見,但在MFC中很少見。不是因爲它的糟糕,而是因爲MFC過於老式和麪向C語言。

順便說一下,更多面向MFC的解決方案將使用Windows消息和MFC Command Routing機制。使用OnCmdMsg覆蓋可以非常快速和輕鬆地完成此操作。

使用顯式接口(特別是事件源和事件偵聽器)將需要更多的時間和精力,但會提供更易讀和可維護的源代碼。

如果你不舒服的事件監聽器,基於Windows消息的設計是比較有前途的路要走比方案1,2

+0

我的直覺告訴我,這實際上是唯一正確的做法,但我不確定MFC中是否有一些快捷方式。在MFC中使用自定義消息不是類型安全的(從WPARAM/LPARAM中轉換),我寧願將事件與Windows中的事件分開,最好使用通用方法 - 我將其添加爲解決方案4. –

+0

將您的答案標記爲接受使用事件監聽器,以保持類的解耦(我選擇的路線來解決這個問題)。謝謝! –

0

也許你可以讓控件公開或使用好友的方法來保存getter,setter的書寫。我認爲這應該在UI類層次結構中被接受,因爲您不會將您的自定義控件設計爲可重用的,所以它們不應該難以安置。它們特定於您正在設計的單個窗口。

所以從層次結構的頂部訪問可能會是這樣做(而且我認爲這將是更好地讓所有的事件處理程序中的一個類別在頂部):

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

,並宣佈GetCtrl2在CLeftPane和CDlg1類友元函數

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

或可選擇地使所有控制元件的公共

UPDATE:我的意思是自定義對話框類的朋友功能不是控件。

+0

我寫道:「所有的控制都是標準的MFC的控制。」所以不能改變他們的代碼,並認爲這不會回答這個問題。我的困境涵蓋了控件容器的設計 - 從對話類開始。我更喜歡保持控件的保護,因爲對話的客戶不應該知道它們。 –

+0

是的,我在那裏犯了一個錯誤。容器類應該具有主窗口的好友功能,以獲取屬於該容器的每個控件,並且它是子容器 – sekmet64

+0

那麼您是否問過如何從不同的地方訪問容器的控件並在鏈中編寫getter/setter爲此或做其他事情。我想最好是將控件設置爲受保護的,但我所說的是控件層次結構基本上是一個單一的代碼單元,那麼爲什麼要將所有內容封裝到容器類中,如果要從事件處理程序訪問它們呢?通過使用朋友函數,您不必使用公共成員,但仍然可以通過單個getter函數訪問它在容器鏈中需要的所有內容。 – sekmet64