Windows Vista for Developers——第二部分:深入分析任務對話方塊

來源:互聯網
上載者:User

作者:Kenny Kerr

翻譯:Dflying Chen

原文:http://weblogs.asp.net/kennykerr/archive/2006/07/18/Windows-Vista-for-Developers-_1320_-Part-2-_1320_-Task-Dialogs-in-Depth.aspx

請同時參考《Windows Vista for Developers》系列。

 

正如Aero嚮導比傳統的嚮導更加友好一樣,替代原有訊息視窗的任務對話方塊(task dialog)也能夠帶來更好的使用者體驗。與訊息視窗相比,任務對話方塊提供了很多新的功能,並大大增強了自訂能力。當然,隨著這些功能上的增強,複雜性也有所提高。在《Windows Vista for Developers》系列的第二部分中,我將用原生C++示範如何有效地使用任務對話方塊API建立各種各樣的對話方塊。如果你沒有耐心,那麼請直接跳到本文的最後找到下載連結,這個連結包含有一個完整的、用C++封裝好的任務對話方塊API的原始碼。

comctl32.dll庫中的一個名為CTaskDialog 的C++類實現了所有任務對話方塊所提供的功能。你可以通過調用comctl32.dll 中的TaskDialog TaskDialogIndirect 函數來使用任務對話方塊。TaskDialog 其實就是個簡單版本的TaskDialogIndirect ,它精簡了TaskDialogIndirect 的很多功能,但也稍微便於使用一些。實際上TaskDialog TaskDialogIndirect 都不是那麼簡單,所以本文將在簡要介紹TaskDialogIndirect 之後,用C++其進行封裝,讓其更加便於使用。

下面的這段代碼將建立一個最簡單的任務對話方塊:

TASKDIALOGCONFIG config = { sizeof (TASKDIALOGCONFIG) };
int selectedButtonId = 0;
int selectedRadioButtonId = 0;
BOOL verificationChecked = FALSE;
HRESULT result = ::TaskDialogIndirect(&config,
                                      &selectedButtonId,
                                      &selectedRadioButtonId,
                                      &verificationChecked);

TASKDIALOGCONFIG 結構包含了建立任務對話方塊所必需的欄位和標記,同時也提供了回呼函數,用來響應任務對話方塊所觸發的事件:

struct TASKDIALOGCONFIG
{
    UINT cbSize;
    HWND hwndParent;
    HINSTANCE hInstance;
    TASKDIALOG_FLAGS dwFlags;
    TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons;
    PCWSTR pszWindowTitle;
    union
    {
        HICON hMainIcon;
        PCWSTR pszMainIcon;
    };
    PCWSTR pszMainInstruction;
    PCWSTR pszContent;
    UINT cButtons;
    const TASKDIALOG_BUTTON* pButtons;
    int nDefaultButton;
    UINT cRadioButtons;
    const TASKDIALOG_BUTTON* pRadioButtons;
    int nDefaultRadioButton;
    PCWSTR pszVerificationText;
    PCWSTR pszExpandedInformation;
    PCWSTR pszExpandedControlText;
    PCWSTR pszCollapsedControlText;
    union
    {
        HICON hFooterIcon;
        PCWSTR pszFooterIcon;
    };
    PCWSTR pszFooter;
    PFTASKDIALOGCALLBACK pfCallback;
    LONG_PTR lpCallbackData;
    UINT cxWidth;
};

可以想象,搞出這樣一個結構出來顯然不是件容易的事情,難免會出現這樣或那樣的錯誤。雖然我們可以忽略其中的很多欄位,但如下一些欄位卻是要得到預期效果所必需的:

  1. cbSize 欄位在編譯期指定了該結構的大小,在C語言中,這種技術常用於區分結構體的版本。作業系統可以通過該欄位知曉該結構的版本,以便基於該版本資訊對結構體中的資料欄位作以假設並使用。
  2. hwndParent 欄位儲存了父視窗的控制代碼。這樣即可讓該對話方塊以模態的形式顯示出來,並可以設定其相對於父視窗的位置。
  3. hInstance 欄位對C++開發人員來說非常有用,允許我們在資源檔中通過標識符指定其字串和表徵圖資源,而不用繁瑣地在代碼中手工載入或建立。
  4. dwFlags 欄位儲存了各種不同的標記,用來控制對話方塊的行為和樣式。本文將在接下來詳細介紹。

 

文本說明(Text Captions)

TASKDIALOGCONFIG 結構包含了如下的欄位,用來分別設定任務對話方塊中的各個文本說明。

pszWindowTitle
pszMainInstruction
pszContent
pszVerificationText
pszExpandedInformation
pszExpandedControlText
pszCollapsedControlText
pszFooter

這些欄位均可以用字串指標,或是用MAKEINTRESOURCE 宏建立的字串標識符進行初始化。除此之外,我們還可以為自訂按鈕設定文本說明,將在下一節中詳細介紹。

就示範了各種不同文本說明的位置:

在對話方塊建立之前,我們可以用pszWindowTitle 設定圖中的“window title”。在對話方塊建立之後,則必須使用SetWindowText 函數對其進行修改。

在對話方塊建立之前,我們即可用pszMainInstruction 設定“main instruction”。在對話方塊建立之後,則必須用TDM_SET_ELEMENT_TEXT 訊息更新該文字。設定WPARAM TDE_MAIN_INSTRUCTION 並設定LPARAM 為一個字串指標,或是一個用MAKEINTRESOURCE 宏建立的字串標識符。“content”、“verification text”、“expanded information”以及“footer”文字也可以用類似的方法設定,只要傳遞給WPARAM 不同的值,用來區分將要操作的各個控制項即可。而“expanded control text”和“collapsed control text” 則只能在對話方塊建立之前分別通過pszExpandedControlText pszCollapsedControlText 欄位設定。版本號碼為5456的Windows Vista在展開/摺疊擴充資訊時還有一個Bug——若該控制項失去輸入焦點,這部分文本將被重設為摺疊狀態時的文字。

設定這些文本說明並不是件容易的事,取決於文本從何處取得以及你希望的設定實際。在本文的稍後部分,我們將用C++對其進行簡化。

 

按鈕

任務對話方塊支援各種普通按鈕以及自訂按鈕的組合。目前普通按鈕有如下幾個:

  1. TDCBF_OK_BUTTON (IDOK)
  2. TDCBF_YES_BUTTON (IDYES)
  3. TDCBF_NO_BUTTON (IDNO)
  4. TDCBF_CANCEL_BUTTON (IDCANCEL)
  5. TDCBF_RETRY_BUTTON (IDRETRY)
  6. TDCBF_CLOSE_BUTTON (IDCLOSE)

我們可以以任何的方式組合這些標記,並設定到dwCommonButtons 欄位中。括弧中的常量是按鈕的標識符,用來甄別使用者點擊了哪個按鈕。

本文結尾部分原始碼下載中的Common Buttons Sample示範了這些普通按鈕:

我們不能直接改變這些普通按鈕的順序以及其文本說明。若想完全控制這些按鈕,則需要提供一個TASKDIALOG_BUTTON結構的數組。下面這段代碼就指定了兩個自訂按鈕:

TASKDIALOGCONFIG config = { sizeof (TASKDIALOGCONFIG) };
TASKDIALOG_BUTTON buttons[] =
{
    { 101, L"First Button"  },
    { 102, L"Second Button" }
};
config.pButtons = buttons;
config.cButtons = _countof(buttons);

我們還可以用MAKEINTRESOURCE 宏指定按鈕將用到的字串資源。除了按鈕之外,任務對話方塊還提供了一系列的單選框。可以用一個TASKDIALOG_BUTTON結構的數組指定:

TASKDIALOG_BUTTON radioButtons[] =
{
    { 201, L"First Radio Button"  },
    { 202, L"Second Radio Button" }
};
config.pRadioButtons = radioButtons;
config.cRadioButtons = _countof(radioButtons);

下面是這段代碼的運行結果:

我們也可以用TDF_USE_COMMAND_LINKS標識將這些自訂按鈕顯示為命令連結。若你不想要連結旁邊的表徵圖的話,那麼應該使用TDF_USE_COMMAND_LINKS_NO_ICON

可以看到,這些標識只能影響到自訂按鈕。普通按鈕將仍顯示為常規樣式。

我們也可以發送TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE訊息至該視窗,這樣即可在連結旁顯示出那個“聲名狼藉的”User Account Control盾形表徵圖(為啥聲名狼藉?——譯者問)。

這條訊息適用於任何自訂按鈕,無論是命令連結形式還是常規按鈕形式。順便說一句,對於OK和Cancel這類的普通按鈕,該訊息同樣適用,雖然這樣做讓使用者看上去並不怎麼友好。

 

表徵圖

我們還可以讓任務對話方塊顯示出一個“主”表徵圖和一個“頁尾”表徵圖。主表徵圖顯示在對話方塊中主要文本的旁邊,若是設定了TDF_CAN_BE_MINIMIZED 標記,那麼也會在視窗的標題列中顯示出來。頁尾表徵圖將顯示在頁尾文字旁。

設定表徵圖則有些技巧。在對話方塊建立之前,我們可以把由MAKEINTRESOURCE 宏建立的表徵圖資源標誌符設定到pszMainIcon 欄位上。如果你選用這種方法,請不要設定TDF_USE_HICON_MAIN 標記。或者也可以把表徵圖控制代碼設定到hMainIcon 欄位中,若採用這種方法,則要確保設定了TDF_USE_HICON_MAIN標記。

頁尾表徵圖也類似。pszFooterIcon 欄位用來在建立對話方塊之前指定表徵圖資源的標識符。或者也可以將表徵圖控制代碼設定到hFooterIcon 欄位中(類似地,要確保設定了TDF_USE_HICON_FOOTER標記)。

在對話方塊建立完成之後,我們可以通過發送TDM_UPDATE_ICON 訊息來更新這些表徵圖。若想更新主表徵圖,那麼將WPARAM 設定為 TDIE_ICON_MAIN ,若想更新頁尾表徵圖,則把WPARAM 設定為TDIE_ICON_FOOTERLPARAM 要設定為表徵圖資源標誌符或者表徵圖控制代碼,這取決於建立對話方塊時TDF_USE_HICON_MAINTDF_USE_HICON_FOOTER 標記的設定。

可以看到,文本說明部分同樣不那麼容易搞定。稍後部分我們的C++解決方案同樣會簡化這一部分的操作。

 

進度條

新的任務對話方塊提供的一個值得稱道的特性就是支援進度條,設定TDF_SHOW_PROGRESS_BAR 標記即可顯示該進度條。若你希望該進度條顯示為走馬燈樣式(就是一小段彩條不停地從左至右滾動——譯者注),可以使用TDF_SHOW_MARQUEE_PROGRESS_BAR 標記。在對話方塊建立之後,你也可以通過發送TDM_SET_PROGRESS_BAR_MARQUEE 訊息在常規進度條和走馬燈進度條樣式之間切換。將WPARAM 設定為TRUE即可切換至走馬燈進度條,設定為FALSE則切換至常規進度條。LPARAM 用於走馬燈樣式的進度條中動畫的延遲時間,單位為毫秒。

發送TDM_SET_PROGRESS_BAR_RANGE 訊息可以指定進度條的指示範圍。LPARAM 的低位元組部分指定範圍的下限,高位元組部分代表上限。TDM_SET_PROGRESS_BAR_POS訊息用來指定進度條在指示範圍中的位置,用WPARAM 設定該位置值。

發送TDM_SET_PROGRESS_BAR_STATE 即可改變該進度條的狀態,WPARAM 可選PBST_NORMALPBST_PAUSEDPBST_ERROR

本文下載代碼中的Progress Sample Progress Effects Sample 示範了所有的進度條功能。

 

通知(Notifications)

任務對話方塊提能夠通知外部程式其目前狀態,可以用來為該對話方塊添加行為或是響應對話方塊中發生的事件。這些通知可以由指定於pfCallback 欄位的回呼函數得到。回呼函數的原型如下:

HRESULT __stdcall Callback(HWND handle, 
                           UINT notification, 
                           WPARAM wParam, 
                           LPARAM lParam, 
                           LONG_PTR data);

這個原型似乎有些誤導的傾向,因為這些訊息都不返回HRESULT。有些訊息有傳回值,但只是布爾值(TRUEFALSE)而已。handle參數提供了任務對話方塊視窗的控制代碼,在TDN_DESTROYED通知到來之前,我們可以隨心所欲地對該對話方塊進行操控。data參數提供了指定於lpCallbackData 欄位中值的指標。常用於將某個C++視窗對象的指標傳遞到靜態回呼函數中。接下來我們看看這些通知。

TDN_DIALOG_CONSTRUCTED是第一個到來的通知。除了提供該任務對話方塊的控制代碼之外,該通知還說明對話方塊建立完畢,即將顯示出來。此時我們即可發送任何用來在對話方塊顯示出來之前修改其樣式的訊息。隨之而來的就是TDN_CREATED 通知,不過通常我們並不需要關心這個東西,除非你要做一些視窗特定的初始化工作。在這兩個通知中做一些初始化工作都是合法的,雖然在頁導航(page navigation )時不會提供TDN_CREATED 通知(但仍會提供TDN_DIALOG_CONSTRUCTED通知)。導航將在稍後討論。

顧名思義,TDN_BUTTON_CLICKED通知表示使用者點擊了某個按鈕,包括普通按鈕和自訂按鈕。若是使用者點擊了對話方塊右上方的X按鈕或是按下了鍵盤上的ESC鍵,那麼該通知同樣會發送,但在建立對話方塊之前要設定TDF_ALLOW_DIALOG_CANCELLATION 標記。WPARAM 指示了被點擊按鈕的標誌符。本文的前面部分已經討論過了按鈕以及按鈕所對應的標誌符。若想關閉該對話方塊,則應該返回FALSE,若不想關閉,則應該返回TRUE

TDN_RADIO_BUTTON_CLICKED通知表示使用者選擇了某個選項按鈕。WPARAM 指示了被選中選項按鈕的標誌符。該通知的傳回值沒什麼用。

TDN_HELP通知表示使用者在鍵盤上按下了F1(協助)鍵。我們最好在這裡提供點協助。

TDN_VERIFICATION_CLICKED通知表示確認複選框的狀態發生了變化。若沒有選中,則WPARAM FALSE,選中了則為TRUE

TDN_EXPANDO_BUTTON_CLICKED通知表示使用者點擊了用來摺疊/展開“expanded information”內容的地區。若處於摺疊狀態,則WPARAM FALSE,若處於展開狀態,則為TRUE

TDN_HYPERLINK_CLICKED 通知表示使用者點擊了位於任務對話方塊中的某個超連結。只有“content”、“expanded information”和“footer”部分才支援超連結,且需要設定TDF_ENABLE_HYPERLINKS 標記。超連結由HTML錨標記(A)定義,例如:

<a href="uri">text</a>

注意這裡只支援雙引號,所以其中的字元可能需要轉義。長字串中同樣可以使用連結。連結的href屬性可通過LPARAM 訪問,然後我們即可隨心所欲地進行處理,例如開啟一個網頁等。任務對話方塊並沒有為連結提供任何預設的行為。下載代碼中的MainWindow 類示範了超連結的使用方法。

TDN_TIMER通知提供了一個定時器,我們可以用這個定時器實現很多不同的功能,例如更新對話方塊中的內容,在一段時間後關閉該對話方塊等。若是設定了TDF_CALLBACK_TIMER 標記,那麼每隔大約200毫秒就會發送一個TDN_TIMER通知。下載代碼中的Timer Sample示範了定時器的功能:

 

訊息(Messages)

任務對話方塊可以響應一系列的訊息,我們可以使用這種機制控制並實現某些需要的行為。

TDM_CLICK_BUTTONTDM_CLICK_RADIO_BUTTON訊息相應地用來類比使用者點擊某個按鈕或選項按鈕。WPARAM 指定了該按鈕或選項按鈕的標識符,LPARAM 在這裡沒什麼用處。

TDM_CLICK_VERIFICATION訊息用來類比使用者點擊了確認複選框。WPARAM 表示該複選框是否被選中,LPARAM 表示該複選框是否需要得到輸入焦點。

TDM_ENABLE_BUTTONTDM_ENABLE_RADIO_BUTTON訊息相應地用來啟用/禁用某個按鈕或選項按鈕。WPARAM 指定了該按鈕或選項按鈕的標識符,LPARAM 表示該按鈕或選項按鈕是否被啟用。

上一節中我沒有提到的一個通知就是TDN_NAVIGATED,因為現在還沒什麼相關的文檔。該通知與TDM_NAVIGATE_PAGE訊息密切相關,所以我覺得把它放在這裡說或許會更好一些。TDM_NAVIGATE_PAGE 同樣也沒什麼相關文檔說明。經過一段時間的單步調試(當然,這裡要使用作業系統的symbol),我終於明白了它的用處。這些訊息可以讓我們從一個任務對話方塊切換,或者叫做導航至另一個,就像一個只能向前的嚮導。“新的”任務對話方塊將得到“老的”任務對話方塊的所有權,也就是說並不需要重新建立一個任務對話方塊。我在反編譯器中跟蹤comctl32.dll 代碼時發現,TDM_NAVIGATE_PAGE 訊息的處理常式並未使用WPARAM ,但卻期待LPARAM 提供一個TASKDIALOGCONFIG 結構,用來描述將要導航至的對話方塊。然後TDN_NAVIGATED 通知就和那個新的任務對話方塊的回呼函數關聯了起來。下載代碼中的Error Sample 示範了這個功能。

 

用C++讓一切變得簡單

任務對話方塊的功能自然強大,但對於開發人員而言,使用起來卻並不是那麼的容易。儘管只暴露出了兩個函數,任務對話方塊的C語言API仍就顯得複雜。為瞭解決這個問題,我特意編寫了這個TaskDialog C++類,簡化我們在C++中操作任務對話方塊的過程。TaskDialog 類繼承於ATL的CWindow 類,它封裝了大部分任務對話方塊的功能,將很多準備TASKDIALOGCONFIG 時所需要的複雜工作抽象了出來,還可以發送訊息並響應通知。下載代碼中的所有程式都使用了該TaskDialog 類,所以你不愁沒有範例程式碼。

下面就是某個樣本任務對話方塊的原始碼,在下載代碼中也可以找到:

class TimerDialog : public Kerr::TaskDialog
{
public:
    TimerDialog() :
        m_reset(false)
    {
        SetWindowTitle(L"Timer Sample");
        SetMainInstruction(L"Time elapsed: 0 seconds");
        AddButton(L"Reset", Button_Reset);
        m_config.dwFlags |= TDF_ALLOW_DIALOG_CANCELLATION | 
                            TDF_CALLBACK_TIMER;
    }
private:
    enum
    {
        Button_Reset = 101
    };
    virtual void OnTimer(DWORD milliseconds, 
                         bool&reset)
    {
        CString text;
        text.Format(L"Time elapsed: %.2f seconds", 
                    static_cast<double>(milliseconds) / 1000);
        SetMainInstruction(text.GetString());
        reset = m_reset;
        m_reset = false;
    }
    virtual void OnButtonClicked(int buttonId, 
                                 bool&closeDialog)
    {
        switch (buttonId)
        {
            case Button_Reset:
            {
                m_reset = true;
                break;
            }
            case IDCANCEL:
            {
                closeDialog = true;
                break;
            }
            default:
            {
                ASSERT(false);
            }
        }
    }
    bool m_reset;
};

可以看到,TaskDialog 類提供了一種簡單的、物件導向的操作任務對話方塊的方式。你再也不需要直接產生那冗長複雜的結構體,也不需要手工定義按鈕數組。這些細節統統都由TaskDialog 類替你搞定。TaskDialog 類提供了設定/修改任務對話方塊中文本說明和表徵圖的方法,還提供了添加按鈕、發送各種訊息的方法,響應通知的功能則用一系列的虛方法提供。

使用上面定義的這個任務對話方塊是件再簡單不過的事了:

TimerDialog dialog;
Dialog.DoModal();

DoModal 方法返回之後,我們即可使用GetSelectedButtonIdGetSelectedRadioButtonIdVerificiationChecked方法取得使用者選擇的按鈕。

若想知道TaskDialog 類到底隱藏了那些複雜性,看看SetWindowTitle 方法的代碼吧:

void Kerr::TaskDialog::SetWindowTitle(ATL::_U_STRINGorID text)
{
    if (0 == m_hWnd)
    {
        m_config.pszWindowTitle = text.m_lpstr;
    }
    else if (IS_INTRESOURCE(text.m_lpstr))
    {
        CString string;
        // Since we know that text is actually a resource Id we can ignore the pointer truncation warning.
        #pragma warning(push)
        #pragma warning(disable: 4311)
        VERIFY(string.LoadString(m_config.hInstance,
                                 reinterpret_cast<UINT>(text.m_lpstr)));
        #pragma warning(pop)
        VERIFY(SetWindowText(string));
    }
    else
    {
        VERIFY(SetWindowText(text.m_lpstr));
    }
}

這裡我使用了ATL的_U_STRINGorID類,方便設定字串指標或是資源標誌符。若尚未建立該任務對話方塊,那麼只要簡單地更新其內部的TASKDIALOGCONFIG 結構即可。否則就要調用SetWindowText 函數來更新視窗的標題。這樣,開發人員在任何時候調用SetWindowTitle 方法都沒有問題,不用再關心文字的來源如何,或是對話方塊是否已經被建立。

 

樣本程式

本文提到的執行個體程式可以在此下載,其中示範了所有文中提到的功能:http://www.kennyandkarin.com/kenny/vista/taskdialogsamplecpp.zip。

 

嗯……似乎這篇文章的長度遠遠超過我的預料(也超出了我的預料,好累——譯者注)。Windows Vista的任務對話方塊API提供了這麼多的功能,我實在是不能再壓縮了。突然意識到,這也是目前唯一一份完整的任務對話方塊參考文檔。希望能夠讓儘可能多的讀者受益。

本來我想在Managed 程式碼中示範任務對話方塊功能的,但Daniel Moth已經用C#做出了一個不錯的版本。他也曾講過一堂webcast ,其中示範了建立任務對話方塊的若干種辦法,包括我在MSDN Magazine文章的中提到的Task Dialog Designer。還要提到的一點就是這個webcast中有處錯誤:其中說Kerr.Vista是一個COM組件,而事實上這隻是個用C++/CLI編寫的簡單.NET程式集。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.