1. 程式人生 > 實用技巧 >寫一個潦草的應用程式使用視覺化元件框架

寫一個潦草的應用程式使用視覺化元件框架

介紹 我寫的上一篇文章介紹了視覺化元件框架的一些基本概念。它的大部分 處理一些我用來讓高階RTTI特性工作的技術,以及其他一些 VCF的核心特性,基礎工具包。在本文中,我們將看到所有的東西實際上是 那是有原因的!我們將介紹組裝一個簡單的塗鴉應用程式的步驟 涵蓋諸如繪圖、事件、自定義VCF元件類和選單等內容。據我所知,你們都氣喘吁吁 有了期待,其實也有了數百萬的粉絲來信,和數十萬的崇拜 粉絲們(哈,哈),讓我們開始吧!! WinMain()去了哪裡? 與任何應用程式一樣,我們需要一個起點,那麼還有什麼比臭名昭著的main()函式更好的起點呢? 哇,夥計,這是Win32,我們說的是這裡!這裡沒有臭味!比爾說我們都是假想的 使用. WinMain () !Wrongo !因為我希望VCF有一個跨平臺的架構,並且在所有其他作業系統中 程式在main()中啟動,我必須弄清楚如何使其工作。謝天謝地,經過仔細檢查 其他人的程式碼(還有其他方法嗎?),我找到了一個這樣做的例子,但仍然在建立windows 我認為這個應用程式是Win32應用程式,而不是控制檯應用程式。通過改變連結器的設定,我們可以替換預設條目 指向其他地方,在本例中是main()函式。如果你檢視任何程式的連結器設定 你在vc++中執行,你會看到子系統被設定為“windows”,這很重要。如果它被設定為“控制檯” 首先出現的是一個控制檯視窗,而不是普通視窗。你仍然可以像平常一樣建立視窗,但是這個 對於表現良好的Windows應用程式來說,這幾乎不是一種行為。因為子系統是windows,所以預設 應用程式的入口點也不同,這是WinMain通常執行的地方。這就是我們的 有個小竅門。我們可以設定入口點符號為“mainCRTStartup”,從而繞過 預設行為,並執行main()函式作為入口點。 好了,好了,技術廢話已經講夠了,讓我們來做點實際的事情吧!任何VCF程式的第一步都是 在堆疊上建立VCF::Application派生物件的例項。如果您不需要覆蓋 任何功能,然後你可以簡單地使用VCF::應用,否則使用你的隱藏複製Code

VCF::Application

derived類。完成之後,只需呼叫VCF::應用程式的靜態方法VCF::Application::appMain(), 傳入main()獲取的argc和argv引數,以及 瞧! 你已經完成了!VCF將為您處理餘下的問題,比如啟動應用程式和呼叫正確的執行 方法。讓我們在程式碼中檢查一下: 隱藏,複製Code

int main(int argc, char *argv[])
{
	VCF::Application app;
	VCF::Application::appMain( argc, argv );
	return 0;
}

這說明只需使用預設的VCF::Application物件。下一個示例使用派生 類建立應用程式物件。 隱藏,複製Code

class ScribbleApplication : public Application {
public:
	ScribbleApplication(){};
	virtual ~ScribbleApplication(){};
};
int main(int argc, char *argv[])
{
	ScribbleApplication app;
	VCF::Application::appMain( argc, argv );
	return 0;
}

現在我們已經提供了自己的ScribbleApplication類,而不是依賴於預設的應用程式 類。那麼在幕後到底發生了什麼呢?基本上,VCF執行時系統負責處理一堆事務 為你準備的東西。應用程式類的建構函式初始化FoundationKit, GraphicsKit,最後是ApplicationKit,它會初始化UIToolkit之類的東西 負責分發視窗特定的對等類,如視窗框架、文字小部件等),以及 用FoundationKit的ClassRegistry註冊所有核心VCF元件類。下一件事 它所做的(這是相當重要的)就是為此將自身設定為當前應用程式物件 程序,可由應用程式的靜態方法Application::getRunningInstance()檢索, 它會返回一個指向app物件的指標。重要的是要注意,您不應該建立 您的程序有多個應用程式或應用程式派生物件。最後它創造了一個特殊的對等,已知 作為一個ApplicationPeer物件,它處理一些與執行應用程式相關的特定於作業系統的任務。 一旦建立了應用程式物件,我們就可以轉移到appMain()方法,它只進行驗證 我們實際上有一個有效執行的應用程式例項,呼叫應用程式對等點上的初始化函式 物件,它在Win32中做一些事情,比如呼叫InitCommonControlsEx()和OleInit() 設定應用程式的命令列(必要時供以後使用),然後呼叫應用程式的第一個命令列 通常覆蓋的方法,initRunningApplication()。 Init是什麼? initRunningApplication()呼叫允許開發人員初始化任何特定於應用程式的內容 他或她想要的啟動程式碼。對於那些熟悉MFC的人來說,它類似於CWinApp的InitInstance() 方法。它通常是建立應用程式主視窗的地方。該函式返回年代真正的 或假,真 可以繼續,false表示錯誤。本申報表 appMain()方法中使用的程式碼用於確定是否繼續,並帶有返回值 表明繼續前進是安全的。此時返回false將導致應用程式的terminateRunningApplication() 方法,然後該程序將清理並退出。terminateRunningApplication () 是您可以重寫的另一種方法,以確保任何特定於應用程式的資料得到正確清理。一次 應用程式已經初始化,對應用程式的run()方法進行了呼叫, 它反過來呼叫對等方的run()方法,然後在其中執行任何特定於作業系統的程式碼。在我們的 case(在Win32下),我們發現一個典型的訊息迴圈(類似於以下內容): 隱藏,複製Code

MSG msg;
HACCEL hAccelTable = NULL;

while ( GetMessage( &msg, NULL, 0, 0 ) ) {		
	if (!TranslateAccelerator( msg.hwnd, hAccelTable, &msg ) ) {
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	}		
}

當迴圈結束(主視窗關閉)時,應用程式的terminateRunningApplication()為 呼叫,允許您優雅地退出並清理特定於應用程式的資料。對等的terminateApp () 方法,該方法執行特定於作業系統的關閉操作。最後,UIToolkit被告知要清理, 然後我們就退出了。作為一個使用VCF的開發人員,您永遠不需要接觸這些東西,但至少它有幫助 要知道發生了什麼。需要注意的主要事情是用於初始化和終止的虛擬方法 通過重寫initRunningApplication()和terminateRunningApplication(). 建立主視窗 現在我們已經瞭解瞭如何建立應用程式物件並開始執行,那麼我們如何建立實際的 窗戶嗎?一旦這個問題得到了答案,哪裡是建立它們的最佳場所?一般來說,這是最好的創作場所 應用程式的視窗在initRunningApplication()方法中,您在 Application-derived類。實際上,建立windows是件容易的事(MFC開發人員應該抓住一些可靠的東西 在這一點上)。您所要做的就是建立一個VCF::Window的新例項(或者派生的類) 它)和presto你有一個視窗,你可以操縱。您還必須設定應用程式的主視窗,因此呼叫 setMainWindow()也必須在這一點上做。 VCF不像其他一些框架那樣使用兩步建立過程來建立控制元件 視窗是派生的)。我相信這極大地簡化了編碼,儘管必須進行一些調整 在基類中(但這對開發人員是隱藏的,並且與派生類無關)。當建立 VCF中的任何型別的元件例項,總是,總是在堆上使用new操作符進行操作。所以對於我們的 主視窗,我們編碼如下: 隱藏,複製Code

Window* mainWindow = new Window();

不 隱藏,複製Code

Window mainWindow;

主視窗屬於應用程式(這就是為什麼我們呼叫setMainWindow()),它將銷燬 當應用程式關閉時。然後,該視窗上的所有元件和控制元件將依次被銷燬 當窗戶被破壞時。這極大地簡化了開發,因為您不必擔心誰破壞 特殊情況是什麼等等。當我們開始介紹新增控制元件時,我們會進一步討論這個問題。 一旦您建立了一個視窗,您就可以輕鬆地設定它的屬性,比如邊界(通過setBounds() 方法)、標題(通過setCaption()方法)以及許多其他方法。我們把標題設為read “VCF Scribble App”,左、右分別為200,200,寬度和高度 分別為500和500。 隱藏,複製Code

mainWindow->setCaption( "VCF Scribble App" );
mainWindow->setBounds( &Rect(200,200,700,700) );

setCaption()採用VCF::字串作為引數,其中VCF::字串 只不過是std::basic_string<VCFChar>的型別定義。最終我想要 編寫一個實際的類,該類具有與std::basic_string相同的STL介面,但也可以處理Unicode, 所以所有內部只使用Unicode作業系統呼叫也可以使用Unicode,但現在是這樣 工作得很好(那些對開發類似的東西感興趣的人,將非常感謝您的幫助!!) 設定邊界需要一個指向Rect類的指標,我們通過一個臨時堆疊物件提供這個類。 矩形有左、上、右和下的成員,因此對於矩形200、200和寬度 高度為500,500時,我們傳入200,200,700,700,這些是Rect建構函式的引數 左邊,上面,右邊,和下面。Rect類在內部以雙精度儲存資料 為了更好的準確性和不需要來回轉換這麼多,允許更少的舍入錯誤(希望如此)。 實際上,如果您檢視VCF應用程式工具包類和圖形工具包類,所有座標都被處理為 雙打。我發現許多視窗系統(Win32除外)都是這樣做的,並決定將其包含在內 這個在VCF。 自定義主視窗 現在我們可以執行我們的應用並顯示一個視窗,但它本身不是hor非常有用,讓我們 繼續並定製一些東西。我們將從VCF::Window派生一個新類,並新增更多控制元件, 以及本文中介紹的其他特性。在第一個過程中,我們將新增兩個面板到 一個視窗將向右對齊,另一個視窗將向其餘視窗對齊 客戶區。讓我們看看程式碼是什麼樣子的,然後我們會介紹它是如何工作的。 隱藏,複製Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        panel1 = new Panel();
        panel1->setBounds( &Rect(0,0,100,200) );
        this->add( panel1, ALIGN_RIGHT );

        panel2 = new Panel();
        this->add( panel2, ALIGN_CLIENT );

    };

    virtual ~ScribbleWindow() {};

    VCF::Panel* panel1;
    VCF::Panel* panel2;
};

如您所見,我們有VCF::Panel類的兩個例項,panel1和panel2,然後建立 他們使用新的操作符。面板是一個簡單的 控制元件實現了VCF::Container介面類,從而允許它容納其他子控制元件。 面板也有一個預設邊框,用於在控制元件的外部繪製3D邊緣。在我們的例子中,我們是 將panel1新增到視窗並將其向右對齊,而我們將新增panel2並將其對齊 剩下的客戶區域。一旦我們建立了panel1,我們就設定它的邊界,其寬度為100。這 因為當控制元件向右或向左對齊時,控制元件仍將保持其原始寬度,但只有 改變它的高度。下一步是新增控制元件,這是通過視窗的add()方法完成的, 在這裡,我們傳遞控制元件和一個enum,它指定了我們想要的對齊方式(我們的選擇是ALIGN_LEFT, ALIGN_RIGHT、ALIGN_TOP、ALIGN_BOTTOM、ALIGN_CLIENT和ALIGN_NONE 用於父控制元件內的固定座標)。與panel1一樣,我們以同樣的方式建立panel2 然後,再次使用add方法,這次傳入panel2,並對齊ALIGN_CLIENT。 注意,我們不必為panel2設定邊界;因為它是新增一個對齊 ALIGN_CLIENT,父容器(在本例中是視窗)將處理重新調整和重新定位 的控制。現在,我們有了一個帶有兩個子控制元件的視窗,這兩個子控制元件都是對齊的,並且將是對齊的 當我們調整父視窗的大小時,自動為我們調整大小。另外,閃爍這個煩人的老問題 窗戶走了! !調整大小到你的心的內容,你將看到沒有閃爍!這是因為VCF會自動 在重繪訊息期間為您提供雙重緩衝。但是,您可以通過設定呼叫控制元件的方法來關閉此功能 方法並傳入false(傳入false) true將再次開啟雙緩衝)。 新增控制元件 向其他控制元件新增控制元件是非常有用和方便的事情,所以讓我們深入一點細節 至於它是如何工作的。可以容納其他控制元件的控制元件稱為容器 在已。任何控制元件都可以是容器,只要它正確地實現了VCF:: container的方法 介面類。為了幫助實現這一點,有一個類實現了這些方法,稱為VCF::AbstractContainer 您可以繼承它來將此功能新增到您的類中。如果我們看一下宣言 Panel類,我們可以看到它是這樣做的。容器的主要方法是 add()方法和remove()方法。add()方法有兩種形式, 首先是新增控制元件,容器將簡單地使用在控制元件中指定的對齊方式 控制元件的對齊屬性,第二個獲取控制元件和對齊型別,以指定如何新增控制元件 當您新增控制元件時,您還需要指定新新增的控制元件的父控制元件,這將發生 自動新增到add()方法中。您不應該自己直接設定控制元件的父類,如 這可能會導致不一致的行為。向父控制元件新增控制元件也意味著父控制元件將 當父元素被刪除時,刪除它的子元素,這樣您就不必擔心釋放相關的記憶體 控制只 在新增控制元件時,可以指定對齊型別,該型別決定容器將如何調整控制元件的大小 孩子控制。對齊到左側(ALIGN_LEFT)將調整控制元件的大小,以便其左側將 始終對齊容器控制元件的客戶端邊緣的最左端,或對齊容器控制元件的右邊緣 以前新增的控制元件左對齊。控制元件將保持其寬度,但頂部、左側和底部將保持寬度 將由容器決定。類似的行為發生在右對齊、頂部對齊或底部對齊(ALIGN_RIGHT, ALIGN_TOP ALIGN_BOTTOM分別)。如果對齊方式為ALIGN_CLIENT, 然後,在其他對齊的控制元件已定位之後,將該控制元件的大小調整為任何剩餘的客戶端空間。 如果控制元件不希望執行任何對齊規則,那麼它的對齊應該設定為ALIGN_NONE, 這是所有基本控制元件的預設對齊方式。ALIGN_NONE表示指定的座標 總是受到尊重,並且在調整容器大小時不會改變。 自定義控制元件:ScribbleView控制元件 這很好,但我們讓它更有趣。我們現在有兩個面板,一個 將容納一些按鈕,另一個將容納一個我們將建立的自定義控制元件作為我們的塗鴉 繪製表面。無論何時您想要在VCF中建立自定義控制元件,您都可以使用幾個選項來繼續操作。 您的第一個問題是,您是想從零開始,還是隻是增強一個已經存在的控制元件, 例如,向Label控制元件新增斜面文字效果。如果你想從頭開始,往往最好的地方 派生自VCF::CustomControl類。你要面對的下一個問題是你是否想要 控制元件可以是“輕”或“重”。重量控制是一種使用 顯示和路由事件的本地視窗系統資源,換句話說,在Win32下,一個控制元件 有一個HWND和一個HDC與之關聯。這方面的一個好例子是視窗或面板控制元件,兩者都是 被認為是重重量控制。另一方面,輕量級控制元件可以與它的使用者共享這些資源 重權重的父節點(在這條線上的某個位置),從而減少了正在使用的本地資源的數量。一個例子 其中的VCF::Label控制元件。這是非常有用的,特別是當你 構建作為另一個更復雜控制元件(如標題或工具欄按鈕)的元件的控制元件。那些 使用過Java的人可以在本地重量級對等類中識別出這一點,而那些來自Delphi的對等類 背景將看到與TWndControl和TGraphicControl類的相似性。為 對於那些有MFC背景的人來說,MFC中沒有可比的類(遺憾的是),所有的都是a 視窗,除非你去麻煩寫一些類似輕量級控制自己的東西。 出於我們的目的,我們將構建一個輕量級的自定義控制元件,因此我們將從VCF::CustomControl派生它。 為了讓控制元件變得更重或更輕,我們向建構函式傳遞一個布林標誌 的VCF::CustomControl, true表示 該控制元件是重量級控制元件,對於輕量級控制元件為false 控制元件(預設值為true)。讓我們看一些程式碼: 隱藏,複製Code

class ScribbleView: public VCF::CustomControl {
public:
    ScribbleView():
    VCF::CustomControl( false )
    {
    }

    virtual ~ScribbleView(){}
};

接下來我們要做的是自定義控制元件的繪製。要做到這一點,我們只需重寫 paint()方法,如有必要,呼叫超類,然後執行我們自己的操作。當 實現paint()方法時,傳入一個指向GraphicsContext物件的指標, 這就是所有繪畫的處理方式。GraphicsContext也包含了所有的繪圖狀態資訊 作為繪製2D原語的各種方法。在進一步深入之前,讓我們先看看程式碼和 然後我會解釋更多。 隱藏,複製Code

class ScribbleView: public VCF::CustomControl {
public:
    ScribbleView():
    VCF::CustomControl( false )
    {
    }

    virtual ~ScribbleView(){}

    virtual void paint( GraphicsContext* ctx ) {
          CustomControl::paint(ctx); 

          Color color(0.85f,0.85f,0.85f);
          ctx->setColor( &color );
          Rect r( 5, 5, getWidth() - 5, getHeight()-5 );
          ctx->rectangle( &r );
          ctx->fillPath();
    }
};

paint()方法的第一行首先呼叫超類paint()方法, 然後建立一個顏色物件,並設定為淺灰色。GraphicsContext的 然後通過呼叫setColor()方法設定顏色。這樣做會導致GraphicsContext 為此後描邊或填充的所有路徑使用指定的顏色。在這之後我們畫了一個矩形 通過呼叫GraphicsContext的rectangle()方法,傳遞一個指向矩形的指標 物件,然後要實際呈現矩形,我們呼叫fillPath(),它填充任何路徑操作 使用GraphicsContext的當前顏色。 GraphicsContext通過指定一系列像PostScript一樣的繪圖操作來工作, 如moveTo, lineTo,矩形,橢圓等,然後你可以填充或描邊路徑生成 之前的功能。填充或描邊路徑之後,路徑操作被刪除,然後繼續。 除了這些較低階的2D原語呼叫之外,還可以使用一系列高階呼叫 繪製更復雜的幾何圖形,以及利用GraphicsContext的轉換矩陣 比如縮放、平移、旋轉和剪下(可能會在另一篇文章中討論)。 這還不算太糟,讓我們把我們的控制元件新增到我們的ScribbleWindow中 只看到它; 隱藏,複製Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        panel1 = new Panel();
        panel1->setBounds( &Rect(0,0,100,200) );
        this->add( panel1, ALIGN_RIGHT );

        panel2 = new Panel();
        this->add( panel2, ALIGN_CLIENT );
                    
        scribView = new ScribbleView();
        panel2->add( scribView, ALIGN_CLIENT );
    };

    virtual ~ScribbleWindow() {};

    VCF::Panel* panel1;
    VCF::Panel* panel2;
    ScribbleView* scribView;
};

滑鼠事件,ScribbleView 現在我們的控制顯示出來,但除此之外它做不了很多。既然我們想在裡面畫畫 連線滑鼠事件的事件處理。一個控制元件有三個方法,可以重寫為這個目的: mouseDown()、mouseMove()和mouseUp(),每個都傳遞一個指標 一個MouseEvent物件。MouseEvent物件有類似於滑鼠按鈕被按下的資訊 還能夠確定當前是否按下了Alt、Control或Shift鍵。通過重寫這些方法 在我們的控制元件上,我們可以自定義控制元件的行為,即每當滑鼠左鍵時繪製線條 按下並拖動滑鼠。所以讓我們看看它的實際效果! 隱藏,收縮,複製Code

class ScribbleView: public VCF::CustomControl {
public:

     //other methods/constructors/destructors omitted...

     virtual void mouseDown( MouseEvent* event ) {
          CustomControl::mouseDown( event );
          dragPt = *event->getPoint();
          GraphicsContext* ctx = this->getContext();
          ctx->moveTo( dragPt.m_x, dragPt.m_y );
     }

     virtual void mouseMove( MouseEvent* event ) {
          CustomControl::mouseMove( event );
          if ( event->hasLeftButton() ) {
               Point* pt = event->getPoint();
               GraphicsContext* ctx = this->getContext();
               ctx->moveTo( dragPt.m_x, dragPt.m_y );
               dragPt = *pt;
               ctx->lineTo( dragPt.m_x, dragPt.m_y );
               ctx->setColor( Color::getColor( "red" ) );
               ctx->strokePath();	
          }
     }

private:
     Point dragPt;
};

我們要覆蓋的兩個方法是mouseDown()和mouseMove()。在這兩種情況下我們確保先呼叫超類的方法,然後再做我們自己的事情。在mouseDown () 方法獲取一個MouseEvent物件的指標,該物件已檢索到當前滑鼠座標 通過呼叫事件的getPoint()方法,並將其儲存在類的成員變數中。在 方法,我們通過一個快速呼叫來驗證是否按下了滑鼠左鍵 返回到MouseEvent的hasLeftButton()方法,該方法將返回true 如果左側按鈕是按下的,則獲取當前點,然後在控制元件的GraphicsContext.上繪製紅線。 新增事件處理程式 現在我們已經完成了基本的ScribbleView類,我們需要連線一些事件處理程式 讓我們的應用程式更有用一些。我們要做的是在右對齊的面板上新增兩個命令按鈕, 這個會被禁用,直到在ScribbleView上繪製了一些東西,這種情況下它會 如果使用者點選它,它會清除ScribbleView。第二個將退出 如果點選應用。為了使外觀更美觀,我們還將在右對齊面板的頂部新增一個標籤,並設定 它的標題讀“命令”。所以,廢話不多說,讓我們看看一些程式碼吧! 隱藏,收縮,複製Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        panel1 = new Panel();
        panel1->setBounds( &Rect(0,0,100,200) );
        this->add( panel1, ALIGN_RIGHT );

        panel2 = new Panel();
        this->add( panel2, ALIGN_CLIENT );

        scribView = new ScribbleView();
        panel2 ->add( scribView, ALIGN_CLIENT );

        label1 = new Label();
        label1->setCaption( "Commands" );
        label1->setBounds( &Rect(10, 10, 80, 35) );
        panel1->add( label1, ALIGN_TOP );

        btn1->setBounds( &Rect(10, 50, 80, 75) );
        panel1->add( btn1 );
        btn1->setCaption( "Clear" );
        btn1->setEnabled( false );
        btn2 = new CommandButton();
        btn2->setBounds( &Rect(10, 90, 80, 115) );
        panel1->add( btn2 );
        btn2->setCaption( "Exit" );
    };

    virtual ~ScribbleWindow() {};

    VCF::Panel* panel1;
    VCF::Panel* panel2;
    ScribbleView* scribView;
};

正如您所看到的,這一部分與我們之前在程式碼的其他部分中看到的非常相似 寫作。標籤和兩個按鈕(btn1和btn2)都是在堆上建立的,然後新增到 它們適當的父控制元件。現在,我們將通過首先在ScribbleWindow中設定函式來新增事件處理 實現我們希望在函式被呼叫時發生的功能。 隱藏,收縮,複製Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        panel1 = new Panel();
        panel1->setBounds( &Rect(0,0,100,200) );
        this->add( panel1, ALIGN_RIGHT );

        panel2 = new Panel();
        this->add( panel2, ALIGN_CLIENT );

        scribView = new ScribbleView();
        panel2 ->add( scribView, ALIGN_CLIENT );

        label1 = new Label();
        label1->setCaption( "Commands" );
        label1->setBounds( &Rect(10, 10, 80, 35) );
        panel1->add( label1, ALIGN_TOP );

        btn1->setBounds( &Rect(10, 50, 80, 75) );
        panel1->add( btn1 );
        btn1->setCaption( "Clear" );
        btn1->setEnabled( false );
        btn2 = new CommandButton();
        btn2->setBounds( &Rect(10, 90, 80, 115) );
        panel1->add( btn2 );
        btn2->setCaption( "Exit" );
    };

    virtual ~ScribbleWindow() {};

    void onScribbleViewMouseUp( MouseEvent* e ) {
        btn1->setEnabled( true );
    }

    void onBtn2Clicked( ButtonEvent* e ) {
        this->close();
    }

    void onBtn1Clicked( ButtonEvent* e ) {
        btn1->setEnabled( false );
        scribView->repaint();
    }

    VCF::Panel* panel1;
    VCF::Panel* panel2;
    ScribbleView* scribView;
};

我們一次只看一個函式。第一個函式onScribbleViewMouseUp()將是 當滑鼠按鈕在ScribbleView控制元件上被釋放時呼叫。當這種情況發生時,啟用 btn1的狀態設定為true,表示該按鈕已啟用 (未變灰),可以接受使用者輸入。下一個函式onBtn2Clicked()將在任何時候呼叫 使用者單擊btn2(或通過程式設計呼叫click()方法),並導致 要關閉的視窗,由於它被設定為應用程式的主視窗,因此也將導致應用程式 戒菸也要乾淨。最後一個函式onBtn1Clicked()在單擊btn1時被呼叫, 它將btn1的enabled狀態設定為false,這將使按鈕變為灰色 輸出(或禁用它),並呼叫ScribbleView的repaint()方法(繼承的方法) ,這反過來導致控制元件清除自己,擦除控制元件中的內容 過程又是; 現在我們已經瞭解了回撥函式的功能,我們如何將它們連線到物件的事件 我們對fire off感興趣嗎?在VCF中,這是通過一個類似於Java偵聽器的系統來完成的 類,它本身是觀察者模式的實現。VCF中的偵聽器是一個c++介面類 定義在事件發生時觸發的一個或多個方法。例如,ButtonListener 介面有一個名為onButtonClicked()的方法,該方法將在偵聽物件時被呼叫 (在本例中是按鈕)觸發適當的事件(對於按鈕,當單擊() 方法被呼叫。要監聽特定物件,我們需要在物件上呼叫適當的add listener方法 願意聽。如果我們想要監聽一個VCF::CommandButton上的按鈕點選事件 物件時,我們將呼叫按鈕的addButtonListener()方法,並傳入一個已實現的物件 ButtonListener c++介面。為了方便這一點,有一些特殊的類,通常是 在與偵聽器介面相同的頭中定義,稱為處理程式,如ButtonHandler類中的處理程式 它實現了ButtonListener介面。處理程式類有一系列成員變數 函式指標,每個為偵聽器介面實現的方法都有一個函式指標。另外,處理程式 也有一個指向作為處理程式源的物件的指標,以及成員函式指標指向的物件。 這聽起來比實際要複雜得多,所以讓我們看一些程式碼,希望能讓事情更清楚一些。 隱藏,收縮,複製Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        //...initialization code omitted 

        ButtonHandler* bh = new ButtonHandler( this );
        bh->m_buttonClicked = (OnButtonEvent)ScribbleWindow::onBtn1Clicked;
        this->addEventHandler( "ButtonHandler", bh );
        btn1->addButtonListener( bh );

        bh = new ButtonHandler( this );
        bh->m_buttonClicked = (OnButtonEvent)ScribbleWindow::onBtn2Clicked;
        this->addEventHandler( "ButtonHandler2", bh );
        btn2->addButtonListener( bh );

        MouseHandler* mh = new MouseHandler( this );
        this->addEventHandler( "MouseHandler", mh );
        mh->m_mouseUp = (OnMouseEvent)ScribbleWindow::onScribbleViewMouseUp;
        view->addMouseListener( mh );
    };

    virtual ~ScribbleWindow() {};

    void onScribbleViewMouseUp( MouseEvent* e ) {
        btn1->setEnabled( true );
    }

    void onBtn2Clicked( ButtonEvent* e ) {
        this->close();
    }

    void onBtn1Clicked( ButtonEvent* e ) {
        btn1->setEnabled( false );
        scribView->repaint();
    }

    VCF::Panel* panel1;
    VCF::Panel* panel2;
    ScribbleView* scribView;
};

我們將首先為按鈕設定事件處理程式,首先為btn1,然後為btn2。 我們首先在堆上使用new建立一個新的ButtonHandler物件 操作符,將源物件傳遞給建構函式,在本例中是ScribbleWindow例項。 ButtonHandler的m_buttonClicked成員變數被設定為指向ScribbleWindow的 onBtn1Clicked()方法,然後將處理程式新增到ScribbleWindow的事件列表 當事件處理程式被銷燬時,它將為我們清除它列表中的所有事件處理程式。最後一步 是實際新增ButtonHandler物件作為按鈕(btn1)的偵聽器,完成了嗎 通過呼叫btn1的addButtonListener()方法並傳入按鈕處理程式物件。在這 點,我們已經連線並準備從btn1接收事件!對另一個也做同樣的事情 兩個事件處理程式。您可能會問,“但是為什麼不直接實現ButtonHandler介面呢? 在塗鴉窗課堂上?”如果你提前知道你只會一直這樣做的話,這是可行的 偵聽一個物件,但是如果您希望偵聽使用同一偵聽器的多個物件,該怎麼辦呢 介面?您最終將使用一系列糟糕的if語句來測試物件的型別,為此只做一件事 物件型別,另一種物件型別對應另一種物件,以此類推。這使它更乾淨(在我看來), 希望能有更好的伸縮性,也能更直接,這樣你就能"看到"那個函式 將被該事件的某某物件呼叫。 有趣的塗鴉選單 在本文中我們要討論的最後一件事是向ScribbleWindow新增選單項。在那裡 選單有兩種主要型別:存在於視窗框架(通常在視窗頂部)的選單和彈出式選單 通常依賴上下文的選單。第一種型別的選單稱為VCF::MenuBar類, 另一種是VCF::PopupMenu。我們的示例將包含這兩種型別,我們的主選單 和一個與我們的ScribbleView控制元件關聯的彈出選單。兩VCF::選單條 和VCF::PopupMenu有一個根選單項,您可以使用它附加其他選單項。最簡單的 附加選單項的方法是建立一個DefaultMenuItem的新例項,傳遞字串標題 選單項的父選單項和該選單項的選單欄或彈出選單放入建構函式中 的新選單項。這將自動將新建立的選單項新增到傳入的父選單項中。新增 選單項的事件處理程式與前面一樣完成,只是我們使用MenuItemHandler。讓我們看 在本文的最後一段程式碼中,我們將看到它的實際應用。 隱藏,收縮,複製Code

class ScribbleWindow : public Window {
public:	
    ScribbleWindow(){
        //...initialization code

        //previous event handler code...
        this->setMenuBar( new MenuBar() );

        MenuBar* menuBar = this->getMenuBar();
        MenuItem* item = menuBar->getRootMenuItem();

        DefaultMenuItem* file = new DefaultMenuItem( "&File", item, menuBar ); 	

        DefaultMenuItem* fileExit = new DefaultMenuItem( "&Exit", file, menuBar ); 

        DefaultMenuItem* view = new DefaultMenuItem( "&View", item, menuBar ); 	

        viewClear= new DefaultMenuItem( "&Clear", view, menuBar ); 	

        MenuItemHandler* menuHandler = new MenuItemHandler( this );
        menuHandler->m_menuItemClicked = (OnMenuItemEvent)ScribbleWindow::onFileExitClicked;
        fileExit->addMenuItemListener( menuHandler );
        this->addEventHandler( "FileExit", menuHandler );

        MenuItemHandler* viewClearMenuHandler = new MenuItemHandler( this );
        viewClearMenuHandler->m_menuItemClicked = (OnMenuItemEvent)ScribbleWindow::onViewClearClicked;
        viewClear->addMenuItemListener( viewClearMenuHandler );
        this->addEventHandler( "viewClear", viewClearMenuHandler );

        PopupMenu* popup = new PopupMenu( this );
		
        DefaultMenuItem* popupRoot = new DefaultMenuItem( "root", NULL, popup );
        DefaultMenuItem* popupEditClear = new DefaultMenuItem( "&Clear", popupRoot, popup );

        popupEditClear->addMenuItemListener( viewClearMenuHandler );

        popup->setRootMenuItem( popupRoot );

        scribbleView->setPopupMenu( popup );
    };

    virtual ~ScribbleWindow() {};

    //previous event handler functions here

    void onFileExitClicked( MenuItemEvent* e ) {
        this->close();
    }

    void onViewClearClicked( MenuItemEvent* e ) {
        btn1->setEnabled( false );
        viewClear->setEnabled( false );
        scribbleView->repaint();
    }

    VCF::Panel* panel1;
    VCF::Panel* panel2;
    ScribbleView* scribView;
    DefaultMenuItem* viewClear;
};

要建立主選單,我們需要建立一個新的VCF::MenuBar,然後設定主視窗的選單欄。 一旦完成,我們就可以像前面描述的那樣開始向根新增選單項。我們建立一個"檔案" 以及“退出”選單項,以及“檢視”和“清除”選單項。就像命令一樣 按鈕,我們建立一個事件處理程式,這一次以VCF::MenuItemHandler的形式,並分配它的 m_menuItemClicked指向ScribbleWindow的onViewClearClicked() 方法。彈出選單也是以同樣的方式建立的,並且它與viewClear選單項共享一個處理程式,since 它只顯示一個命令“Clear”。 唉呀!…這是所有人 我們有一個Scribble應用,它有大約200行程式碼,它的子程式碼都是自動對齊的 視窗,雙緩衝,允許你在左手邊畫畫/塗鴉,清除螢幕,退出應用, 當繪圖的狀態發生變化時,也會更新clear按鈕的狀態。這還包括事件 處理各種我們想要監聽並在它們觸發事件時響應的物件。希望我講完了 解釋所有這些是如何工作的,並提供可供開發的備選c++體系結構,這是一個合理的工作 Win32應用程式。 本文轉載於:http://www.diyabc.com/frontweb/news12330.html