<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # Part X - Implementing a Drag and Drop Source 原作 :[**Michael Dunn**](http://www.codeproject.com/wtl/WTL4MFC10.asp) 翻譯 :[yaker](http://www.yakergong.com/blog) ## 內容 * [簡介](#intro) * [創建工程](#starting) * [處理 File-Open 操作](#fileopen) * [拖動源](#dragsource) * [拖動源的接口](#dragsrcitf) * [調用者的輔助方法](#dragsrchelpers) * [IDropSource接口的方法](#IDropSource) * [查看器里拖放操作的實現](#appdragdrop) * [添加一個最近使用文件列表](#mru) * [設置MRU對象](#mrusetup) * [處理MRU命令并更新列表](#handlingmru) * [保存MRU列表](#savemru) * [其他的UI相關的東西](#otherui) * [半透明的拖放效果](#dragimage) * [半透明的矩形選擇框](#alphamarquee) * [按列排序](#lvsortcol) * [使用平鋪視圖模式](#lvtilemode) * [設置平鋪視圖圖像列表](#tileimglistsetup) * [使用平鋪視圖圖片列表](#tileimglistusing) * [設置而外的幾行文字](#tileaddllines) * [版權與協議](#copying) * [修訂歷史](#revisionhistory) ## 簡介 支持拖放操作是很多現代程序的特性。雖然實現拖動源很直接,但是釋放目標則要復雜得多。MFC 中的類 `COleDataObject` 和 `COleDropSource` 可以輔助管理拖動源所必須提供的數據,可是WTL中并沒有提供這樣的輔助類。對我們這些WTL用戶來說,幸運的是: [Raymond Chen](http://blogs.msdn.com/oldnewthing/) 寫了一篇MSDN文章 ("[The Shell Drag/Drop Helper Object Part 2](http://msdn.microsoft.com/library/en-us/dnwui/html/ddhelp_pt2.asp)") ,文中提供了一個 `IDataObject`的純C++語言實現,這對于在WTL程序中實現拖放操作來說是一個巨大的幫助。 這篇文章的樣例工程是一個CAB文件查看工具,它支持通過將文件從查看工具窗口拖動到windows文件夾窗口來實現解壓操作。這篇文章也將討論一些關于框架窗口的主題,比如處理 File-Open 操作和與MFC中文檔視圖框架類似的數據管理。我也將介紹 WTL的 MRU (最近經常使用,most-recently-used) 文件列表類,還有一些6.0版本列表視圖空間的一些新特性。 **注意**: 你需要下載安裝 Microsoft 的 CAB SDK才能編譯樣例代碼。Microsoft的Konwledge Base網站中的一篇文章里有CAB SDK的鏈接: [Q310618](http://support.microsoft.com/?kbid=310618). 樣例程序假定SDK被放置在源代碼目錄下名為"cabsdk"的目錄里。 注意,如果你在安裝WTL或者編譯樣例代碼時遇到任何問題,在提問之前請閱讀 [第一部分里 readme 這一節](./parti.html#readme) ## 創建工程 現在開始創建我們的 CAB 查看器程序,運行WTL AppWizard 然后創建一個名為 _WTLCabView_ 的工程。它是一個SDI(single document interface,單文檔界面)應用程序,在第一頁選擇“SDI Application”: ![](https://box.kancloud.cn/2016-01-13_56962fba8fb62.png) 下一頁,取消選中 _Command Bar_ ,然后將 _View Type_ 改為 _List View_. 向導會為我們的視圖窗口創建一個C++類,賓切它繼承自 `CListViewCtrl` 類。 ![](https://box.kancloud.cn/2016-01-13_56962fba9c6ba.png) 視圖窗口類看起來像這樣: ``` class CWTLCabViewView : public CWindowImpl&lt;CWTLCabViewView, CListViewCtrl&gt; { public: DECLARE_WND_SUPERCLASS(NULL, CListViewCtrl::GetWndClassName()) // Construction CWTLCabViewView(); // Maps BEGIN_MSG_MAP(CWTLCabViewView) END_MSG_MAP() // ... }; ``` 和[第二部分](./PartII.htm)我們使用的視圖窗口一樣,我們可以使用`CWindowImpl`的第三方模板參數設置默認窗口風格: ``` #define VIEW_STYLES \ (LVS_REPORT | LVS_SHOWSELALWAYS | \ LVS_SHAREIMAGELISTS | LVS_AUTOARRANGE ) #define VIEW_EX_STYLES (WS_EX_CLIENTEDGE) class CWTLCabViewView : public CWindowImpl&lt;CWTLCabViewView, CListViewCtrl, CWinTraitsOR&lt;VIEW_STYLES,VIEW_EX_STYLES&gt; &gt; { //... }; ``` 因為WTL不包含 文檔/視圖 框架,視圖類要承擔UI和保存CAB文件信息。拖放操作過程中操作的數據結構是 `CDraggedFileInfo`: ``` struct CDraggedFileInfo { // Data set at the beginning of a drag/drop: CString sFilename; // name of the file as stored in the CAB CString sTempFilePath; // path to the file we extract from the CAB int nListIdx; // index of this item in the list ctrl // Data set while extracting files: bool bPartialFile; // true if this file is continued in another cab CString sCabName; // name of the CAB file bool bCabMissing; // true if the file is partially in this cab and // the CAB it's continued in isn't found, meaning // the file can't be extracted CDraggedFileInfo ( const CString& s, int n ) : sFilename(s), nListIdx(n), bPartialFile(false), bCabMissing(false) { } }; ``` 視圖類對于初始化,操作文件列表和在開始拖放操作時建立一個 `CDraggedFileInfo` 的列表相應的方法(函數)。我不想花費太多時間解釋UI的內部工作原理,因為這篇文章是關于拖放操作的實現的,所以關于UI的部分請參考工程里的 _WTLCabViewView.h_ 文件。 ## 處理 File-Open 操作 想要查看一個CAB文件,用戶可以使用 _File-Open_ 命令,然后選擇一個CAB文件。向導為 `CMainFrame` 生成的代碼包含了處理 _File-Open_ 菜單項的代碼: ``` BEGIN_MSG_MAP(CMainFrame) COMMAND_ID_HANDLER_EX(ID_FILE_OPEN, OnFileOpen) END_MSG_MAP() ``` `OnFileOpen()` 使用了 `CMyFileDialog` 類,在 [第四部分](./PartIX.htm#usingcfiledialog) 中介紹的改進版的 `CFileDialog` 類,來顯示一個標準的打開文件對話框。 ``` void CMainFrame::OnFileOpen ( UINT uCode, int nID, HWND hwndCtrl ) { CMyFileDialog dlg ( true, _T("cab"), 0U, OFN_HIDEREADONLY|OFN_FILEMUSTEXIST, IDS_OPENFILE_FILTER, *this ); if ( IDOK == dlg.DoModal(*this) ) ViewCab ( dlg.m_szFileName ); } ``` `OnFileOpen()` 調用了 `ViewCab()`的幫助函數: ``` void CMainFrame::ViewCab ( LPCTSTR szCabFilename ) { if ( EnumCabContents ( szCabFilename ) ) m_sCurrentCabFilePath = szCabFilename; } ``` `EnumCabContents()` 函數比較復雜,并且使用了 CAB SDK 調用來枚舉 `OnFileOpen()`里選中CAB文件中的內容,并且填充視圖窗口。雖然目前 `ViewCab()` 的功能還不夠,我們會逐漸添加代碼來實現更多的功能。這里 CAB查看器 打開一個CAB文件時的效果: ![](https://box.kancloud.cn/2016-01-13_56962fbebda3a.gif) `EnumCabContents()` 在視圖類中使用了兩個方法來填充UI: `AddFile()` 和 `AddPartialFile()`。當一個文件部分存儲于該CAB文件(其余的部分在另外的CAB文件內)時調用 `AddPartialFile()` 方法。上圖所示的截圖中,列表中的第一個文件就是部分存儲于該CAB文件中。剩余的項使用 `AddFile()` 方法添加到視圖窗口中。這兩種方法都為添加的文件創建了同一種數據結構,所以視圖能夠獲得它所顯示的文件的細節信息。 如果 `EnumCabContents()` 返回值是 true,那說明枚舉過程和UI建立都成功的執行。如果我們僅僅是想寫個簡單的CAB查看器,現在做的這些就已經足夠了,但是程序就不會那么有趣了。要讓這個工具變得真正易用起來,我們要為它添加拖放操作使得用戶可以通過拖動來解壓文件。 ## 拖動源 拖動源是實現了以下兩個接口的 COM對象: `IDataObject` 和 `IDropSource`. `IDataObject` 用來存儲拖放操作過程中客戶端想要傳輸的所有數據;對我們來說就是一個 `HDROP` 結構,結構體里保存要從CAB文件里解壓出來的文件列表 。OLE在拖放操作過程中調用 `IDropSource` 接口來通知事件的來源。 ### 拖動源的接口 實現了拖動源的C++類是 `CDragDropSource`. 它開始于 [這篇MSDN文章](http://msdn.microsoft.com/library/en-us/dnwui/html/ddhelp_pt2.asp) 里描述的 `IDataObject` 的實現 ,簡介里我們介紹了這篇文章。在那篇文章里你能找到關于這段代碼的全部細節信息,這里我就不在贅述了。接下來我們向類中添加了 `IDropSource` 和它的兩個方法: ``` class CDragDropSource : public CComObjectRootEx&lt;CComSingleThreadModel&gt;, public CComCoClass&lt;CDragDropSource&gt;, public IDataObject, public IDropSource { public: // Construction CDragDropSource(); // Maps BEGIN_COM_MAP(CDragDropSource) COM_INTERFACE_ENTRY(IDataObject) COM_INTERFACE_ENTRY(IDropSource) END_COM_MAP() // IDataObject methods not shown... // IDropSource STDMETHODIMP QueryContinueDrag ( BOOL fEscapePressed, DWORD grfKeyState ); STDMETHODIMP GiveFeedback ( DWORD dwEffect ); }; ``` ### 調用者的輔助方法 `CDragDropSource` 使用了一些輔助方法包裝了 `IDataObject`的管理和拖放操作過程中的通信。一次拖放操作遵循以下模式: 1. 用戶開始一次拖放操作時主框架得到通知。 2. 主框架調用視圖窗口的方法來創建一個被拖動的文件的列表。視圖窗口類使用一個 `vector&lt;CDraggedFileInfo&gt;`結構返回這些信息。 3. 主框架創建一個 `CDragDropSource` 對象并且把 vector&lt;CDraggedFileInfo&gt;傳遞給它,這樣它就可以了解要從CAB里解壓的文件的信息。 4. 主框架開始拖放操作。 5. 如果用戶在一個適當的位置釋放目標,`CDragDropSource` 對象會解壓縮相應的文件。 6. 主框架更新UI來指出任何未能解壓的文件。 第 3-6 步是通過輔助方法來實現的。初始化功能由 `Init()` 方法實現: ``` bool Init(LPCTSTR szCabFilePath, vector&lt;CDraggedFileInfo&gt;& vec); ``` `Init()` 會復制數據到受保護(protected)的成員變量里,填充到一個 `HDROP` 結構里,并且存儲起來。`Init()` 所做的另外一項重要工作就是:它在臨時目錄為每個被拖放的文件創建了一個0比特的臨時文件。舉個例子,比如用戶拖動了CAB文件內的 _buffy.txt_ 和 _willow.txt_ 兩個文件, `Init()` 函數會在臨時目錄創建兩個相應的同名文件。僅當釋放目標驗證了從`HDROP`里讀出的文件名的合法性之后才會產生這樣的操作,如果文件不存在,釋放操作會失敗。 下一個要介紹的函數是 `DoDragDrop()`: ``` HRESULT DoDragDrop(DWORD dwOKEffects, DWORD* pdwEffect); ``` `DoDragDrop()` 從參數 `dwOKEffects` 里獲取了一系列 `DROPEFFECT_*` 標志位,說明了拖動源上允許進行的操作。它查詢必要的借口,然后調用系統API `DoDragDrop()`。若果拖放成功,`*pdwEffect` 被置為 `DROPEFFECT_*` 系列的值,該值正好反映了用戶想做的操作。 最后一個方法是 `GetDragResults()`: ``` const vector&lt;CDraggedFileInfo&gt;& GetDragResults(); ``` `CDragDropSource` 對象維護了一個 `vector&lt;CDraggedFileInfo&gt;` 結構,在拖放操作過程中這個結構也被更新了。如果一個文件只是部分的存在于這個CAB文件中,或者解壓縮錯誤,`CDraggedFileInfo` 都會被更新。主框架調用 `GetDragResults()` 來獲取這個vector,所以它能夠檢查錯誤,并相應地更新UI。 ### IDropSource接口的方法 `IDropSource` 接口要提供的第一個方法是 `GiveFeedback()`,它用來通知拖動源用戶想要做的操作(移動,復制或者鏈接)。如果需要的話,拖動源也可以更改光標。`CDragDropSource` 跟蹤用戶操作,并且通知OLE使用默認的拖放圖標。 ``` STDMETHODIMP CDragDropSource::GiveFeedback(DWORD dwEffect) { m_dwLastEffect = dwEffect; return DRAGDROP_S_USEDEFAULTCURSORS; } ``` 另外一個 `IDropSource` 方法是 `QueryContinueDrag()`. 當用戶移動光標的時候OLE調用這個方法,并且通知拖動源哪些鼠標鍵和鍵盤按鍵被按下。如下是多數 `QueryContinueDrag()` 實現所采用的樣例代碼。 ``` STDMETHODIMP CDragDropSource::QueryContinueDrag ( BOOL fEscapePressed, DWORD grfKeyState ) { // If ESC was pressed, cancel the drag. // If the left button was released, do drop processing. if ( fEscapePressed ) return DRAGDROP_S_CANCEL; else if ( !(grfKeyState & MK_LBUTTON) ) { // If the last DROPEFFECT we got in GiveFeedback() // was DROPEFFECT_NONE, we abort because the allowable // effects of the source and target don't match up. if ( DROPEFFECT_NONE == m_dwLastEffect ) return DRAGDROP_S_CANCEL; // TODO: Extract files from the CAB here... return DRAGDROP_S_DROP; } else return S_OK; } ``` 鼠標左鍵釋放的時候,選中文件從CAB文件中釋放出來。 ``` STDMETHODIMP CDragDropSource::QueryContinueDrag ( BOOL fEscapePressed, DWORD grfKeyState ) { // If ESC was pressed, cancel the drag. // If the left button was released, do the drop. if ( fEscapePressed ) return DRAGDROP_S_CANCEL; else if ( !(grfKeyState & MK_LBUTTON) ) { // If the last DROPEFFECT we got in GiveFeedback() // was DROPEFFECT_NONE, we abort because the allowable // effects of the source and target don't match up. if ( DROPEFFECT_NONE == m_dwLastEffect ) return DRAGDROP_S_CANCEL; // If the drop was accepted, do the extracting here, // so that when we return, the files are in the temp dir // and ready for Explorer to copy. if ( ExtractFilesFromCab() ) return DRAGDROP_S_DROP; else return E_UNEXPECTED; } else return S_OK; } ``` `CDragDropSource::ExtractFilesFromCab()` 是另外一段比較復雜的代碼,它使用了 CAB SDK 來解壓文件到臨時目錄,覆蓋我們之前創建的0字節文件。`QueryContinueDrag()` 返回 `DRAGDROP_S_DROP`時,它通知OLE完成拖放操作。如果釋放目標是一個Windows資源瀏覽器窗口,Explorer會從臨時目錄復制文件到拖放操作的目標文件夾。 ## 查看器里拖放操作的實現 我們已經說明了實現拖放邏輯的類,接下來讓我們看一下查看器是如何使用這些類的。當主框架窗口接收到一個 `LVN_BEGINDRAG` 消息,它調用視圖來獲取一個被選中文件的列表,然后建立一個 `CDragDropSource` 對象: ``` LRESULT CMainFrame::OnListBeginDrag(NMHDR* phdr) { vector&lt;CDraggedFileInfo&gt; vec; CComObjectStack&lt;CDragDropSource&gt; dropsrc; DWORD dwEffect = 0; HRESULT hr; // Get a list of the files being dragged (minus files // that we can't extract from the current CAB). if ( !m_view.GetDraggedFileInfo(vec) ) return 0; // do nothing // Init the drag/drop data object. if ( !dropsrc.Init(m_sCurrentCabFilePath, vec) ) return 0; // do nothing // Start the drag/drop! hr = dropsrc.DoDragDrop(DROPEFFECT_COPY, &dwEffect); return 0; } ``` 第一個調用的方法是視圖的 `GetDraggedFileInfo()` 方法,用來獲取被選擇文件的列表。該方法返回一個 `vector&lt;CDraggedFileInfo&gt;`結構,我們使用這個結構來初始化 `CDragDropSource` 對象。如果被選中的文件都不能解壓縮(比如文件都部分的存儲于該CAB中),`GetDraggedFileInfo()` 可能會失敗。如果`GetDraggedFileInfo()` 失敗, `OnListBeginDrag()` 也會失敗并切不做任何操作直接返回。最后我們調用 `DoDragDrop()` 進行拖放操作,由 `CDragDropSource` 完成剩下的事情。 上面所提到的列表的第六步--即更新UI,在拖放操作之后完成。處于CAB壓縮包末尾的文件可能只是部分的存儲于該CAB中,剩下的部分在后面的CAB文件中。(這對于 Windows 9x 系列安裝文件來說很普通,因為需要限制單個 CAB 文件的大小使得能夠放入軟盤中)。我們試圖解壓這樣一個文件的時候,CAB SDK會告訴我們包含該文件剩余部分的CAB文件么名。它會在相同目錄下尋找包含該文件的起始CAB文件,并且解壓接下來的CAB文件(如果存在)。 當我們想要指出視圖窗口中的部分存儲文件的時候,,`OnListBeginDrag()` 檢查拖放結果看是否有部分存儲文件: ``` LRESULT CMainFrame::OnListBeginDrag(NMHDR* phdr) { //... // Start the drag/drop! hr = dropsrc.DoDragDrop(DROPEFFECT_COPY, &dwEffect); if ( FAILED(hr) ) ATLTRACE("DoDragDrop() failed, error: 0x%08X\n", hr); else { // If we found any files continued into other CABs, update the UI. const vector&lt;CDraggedFileInfo&gt;& vecResults = dropsrc.GetDragResults(); vector&lt;CDraggedFileInfo&gt;::const_iterator it; for ( it = vecResults.begin(); it != vecResults.end(); it++ ) { if ( it-&gt;bPartialFile ) m_view.UpdateContinuedFile ( *it ); } } return 0; } ``` 我們調用 `GetDragResults()` 來獲取更新過得 `vector&lt;CDraggedFileInfo&gt;` 結構,它反映了拖放操作的輸出結果。如果成員變量 `bPartialFile` 被設置為 `true`,那說明該文件部分存儲于 CAB 文件中。我們使用 `UpdateContinuedFile()` 來處理剩下的工作,把相應的 CDraggedFileInfo 結構體傳給它,使得它能夠更新該文件相應的視圖列表項目。下圖說明了當程序指出一個文件部分的存儲于該 CAB 中,并且顯示出下一步分所在文件的情形: ![](https://box.kancloud.cn/2016-01-13_56962fbecd335.png) 如果后續 CAB 文件無法找到,程序會通過設置該項樣式為 `LVIS_CUT` 表明該文件無法解壓,同時圖標變為灰色。 ![](https://box.kancloud.cn/2016-01-13_56962fbeefd80.png) 出于安全的考慮,程序將解壓出的文件留在臨時目錄中,而不是拖放操作完成后立即清除它們。當 `CDragDropSource::Init()` 創建0字節文件的時候,它也把每個文件名添加到一個全局 vector `g_vecsTempFiles`中。當主框架窗口關閉的時候臨時文件才會被清除。 ## 添加一個最近使用文件列表 下面我們要探討的文檔/視圖樣式特性就是一個最近使用文件列表(MRU)。WTL的MRU實現是一個模板類: `CRecentDocumentListBase`. 如果你不需要重載默認MRU的任何行為(默認行為通常很重要),你可以使用派生類 `CRecentDocumentList`. `CRecentDocumentListBase` 模板類有如下參數: ``` template &lt;class T, int t_cchItemLen = MAX_PATH, int t_nFirstID = ID_FILE_MRU_FIRST, int t_nLastID = ID_FILE_MRU_LAST&gt; CRecentDocumentListBase ``` `T` 用來特化 `CRecentDocumentListBase` 的派生類名。 `t_cchItemLen` 要存在MRU列表中的項的長度,以 `TCHAR`計。該項至少為6。 `t_nFirstID` MRU項所使用的ID中的最小ID。 `t_nLastID` MRU項所使用的ID中的最大ID。 該項必須大于 `t_nFirstID`。 要為我們的程序加入MRU特性,只需要幾步。 1. 插入一個ID為 `ID_FILE_MRU_FIRST` 的菜單項。菜單項文字設置為若MRU列表是空時你希望顯示的消息。 2. 添加一個ID為 `ATL_IDS_MRU_FILE`的字符串表(string table)。這個字符串表用來顯示MRU項選中時的浮動提示。如果你使用 WTL AppWizard 來生成工程,該字符串默認已經創建。 3. 向 `CMainFrame` 添加一個 `CRecentDocumentList` 對象。 4. 在 `CMainFrame::Create()` 里初始化這個對象。 5. 處理ID在`ID_FILE_MRU_FIRST` 和 `ID_FILE_MRU_LAST` 之間的 `WM_COMMAND` 消息。 6. 打開一個CAB文件時更新MRU列表。 7. 應用程序關閉時保存MRU列表。 另外,如果 `ID_FILE_MRU_FIRST` and `ID_FILE_MRU_LAST` 對于你的程序來說不合適,你可以通過一個新的特化的 `CRecentDocumentListBase`類來替換它們。 ### 設置MRU對象 第一步是添加一個菜單項指明MRU列表的位置。通常將MRU文件列表放置于 _File_ 菜單下,我們的程序里也是這么做的。菜單項的位置如下圖所示: ![](https://box.kancloud.cn/2016-01-13_56962fbf0e534.png) WTL AppWizard already 添加了ID為 `ATL_IDS_MRU_FILE` 字符串到字符串表里,我們將它的內容修改為 "Open this CAB file"。接下來我們添加一個 `CRecentDocumentList` 成員變量到 `CMainFrame`中,變量名是 `m_mru`,然后在 `OnCreate()`將其初始化: ``` #define APP_SETTINGS_KEY \ _T("software\\Mike's Classy Software\\WTLCabView"); LRESULT CMainFrame::OnCreate ( LPCREATESTRUCT lpcs ) { HWND hWndToolBar = CreateSimpleToolBarCtrl(...); CreateSimpleReBar ( ATL_SIMPLE_REBAR_NOBORDER_STYLE ); AddSimpleReBarBand ( hWndToolBar ); CreateSimpleStatusBar(); m_hWndClient = m_view.Create ( m_hWnd, rcDefault ); m_view.Init(); // Init MRU list CMenuHandle mainMenu = GetMenu(); CMenuHandle fileMenu = mainMenu.GetSubMenu(0); m_mru.SetMaxEntries(9); m_mru.SetMenuHandle ( fileMenu ); m_mru.ReadFromRegistry ( APP_SETTINGS_KEY ); // ... } ``` 前兩個被調用的方法用于設置MRU中項的數目(默認值是16),并且將該成員變臉關聯到菜單上。`ReadFromRegistry()` 從注冊表中讀取MRU列表。它接受我們傳遞的鍵,然后在相應位置創建一個新的鍵來保存列表。以我們的程序為例,鍵的值是 `HKCU\Software\Mike's Classy Software\WTLCabView\Recent Document List`。 導入文件列表后, `ReadFromRegistry()` 調用另外一個 `CRecentDocumentList` 方法`UpdateMenu()`,它查找MRU菜單項并且使實際的MRU項替代它的內容。 ### 處理MRU命令并更新列表 當用戶選中一個MRU項時,主框架窗口會收到一個 `WM_COMMAND` 消息,消息的command ID等于菜單項的ID。我們可以使用一條宏語句來處理整個消息映射。 ``` BEGIN_MSG_MAP(CMainFrame) COMMAND_RANGE_HANDLER_EX( ID_FILE_MRU_FIRST, ID_FILE_MRU_LAST, OnMRUMenuItem) END_MSG_MAP() ``` 消息處理函數從MRU對象中獲取選中項的完整路徑,然后調用 `ViewCab()` 方法,這樣應用程序就顯示出該文件的內容。 ``` void CMainFrame::OnMRUMenuItem ( UINT uCode, int nID, HWND hwndCtrl ) { CString sFile; if ( m_mru.GetFromList ( nID, sFile ) ) ViewCab ( sFile, nID ); } ``` 正如前面提到的一樣,我們擴展了 `ViewCab()` 方法使得它能夠獲取MRU對象的信息,并且更新MRU文件列表。ViewCab() 方法原型如下: ``` void ViewCab ( LPCTSTR szCabFilename, int nMRUID = 0 ); ``` 如果 `nMRUID` 值為 0,那么`ViewCab()` 方法是通過 `OnFileOpen()`調用的。否則,就是用戶選中MRU菜單項調用的,并且 `nMRUID` 的值為 `OnMRUMenuItem()` 所接收到的值。下面是更新后的代碼: ``` void CMainFrame::ViewCab ( LPCTSTR szCabFilename, int nMRUID ) { if ( EnumCabContents ( szCabFilename ) ) { m_sCurrentCabFilePath = szCabFilename; // If this CAB file was already in the MRU list, // move it to the top of the list. Otherwise, // add it to the list. if ( 0 == nMRUID ) m_mru.AddToList ( szCabFilename ); else m_mru.MoveToTop ( nMRUID ); } else { // We couldn't read the contents of this CAB file, // so remove it from the MRU list if it was in there. if ( 0 != nMRUID ) m_mru.RemoveFromList ( nMRUID ); } } ``` 如果 `EnumCabContents()` 沒有失敗,我們就根據選中該文件的不同情況來更新MRU列表。如果是通過 _File-Open_ 選中的,我們調用 `AddToList()` 方法把文件添加到MRU列表中。如果是通過MRU菜單項選中的,我們使用 `MoveToTop()` 方法把它移動到列表的頂端。如果 `EnumCabContents()` 方法失敗,我們要調用 `RemoveFromList()` 方法從列表中移除該文件。這些方法都會在內部調用 `UpdateMenu()` 方法,所以 _File_ 菜單也會自動得到更新。 ### 保存MRU列表 應用程序關閉時,我們保存MRU列表到注冊表中。這個很簡單,一行代碼搞定: ``` m_mru.WriteToRegistry ( APP_SETTINGS_KEY ); ``` 這行代碼在 `CMainFrame` 里與 `WM_DESTROY` 和 `WM_ENDSESSION` 對應的消息處理函數中調用。 ## 其他的UI相關的東西 ### 半透明的拖放效果 Windows 2000 以及后續版本的windows操作系統有一個內置的 COM 對象: drag/drop helper,用來在拖放操作過程中提供一個很好的半透明效果。拖動源可以通過 `IDragSourceHelper` 接口使用這個對象。下面是些額外的代碼,加粗標記過,把它添加到 `OnListBeginDrag()` 方法來使用helper 對象: ``` LRESULT CMainFrame::OnListBeginDrag(NMHDR* phdr) { NMLISTVIEW* pnmlv = (NMLISTVIEW*) phdr; CComPtr&lt;IDragSourceHelper&gt; pdsh; vector&lt;CDraggedFileInfo&gt; vec; CComObjectStack&lt;CDragDropSource&gt; dropsrc; DWORD dwEffect = 0; HRESULT hr; if ( !m_view.GetDraggedFileInfo(vec) ) return 0; // do nothing if ( !dropsrc.Init(m_sCurrentCabFilePath, vec) ) return 0; // do nothing // Create and init a drag source helper object // that will do the fancy drag image when the user drags // into Explorer (or another target that supports the // drag/drop helper interface). hr = pdsh.CoCreateInstance ( CLSID_DragDropHelper ); if ( SUCCEEDED(hr) ) { CComQIPtr&lt;IDataObject&gt; pdo; if ( pdo = dropsrc.GetUnknown() ) pdsh-&gt;InitializeFromWindow ( m_view, &pnmlv-&gt;ptAction, pdo ); } // Start the drag/drop! hr = dropsrc.DoDragDrop(DROPEFFECT_COPY, &dwEffect); // ... } ``` 我們從創建drag/drop helper COM對象開始。如果成功了,我們調用 `InitializeFromWindow()` 方法并且傳遞三個參數:拖動源窗口的 `HWND` 句柄,光標的位置,以及一個 `CDragDropSource` 對象上的 `IDataObject` 接口。drag/drop helper 使用這個接口來存儲它自己的數據,并且如果釋放目標也使用了helper 對象,這些數據用來生成拖動圖像。 為了使 `InitializeFromWindow()` 工作起來,拖動源窗口需要處理`DI_GETDRAGIMAGE` 消息,并且創建一個做為拖動圖片的位圖回應消息。幸運的是,列表視圖控件支持這個特性,所以不需要太多工作就可以得到拖動圖片。效果圖如下圖所示: ![](https://box.kancloud.cn/2016-01-13_56962fbf19850.gif) 如果我們使用其他類型的窗口做為視圖類,這種況口恰好不能處理 `DI_GETDRAGIMAGE` 消息,我們可以自己創建拖動圖并調用 `InitializeFromBitmap()` 方法來存儲到drag/drop helper對象中。 ### 半透明的矩形選擇框 從Windows XP開始,列表視圖空間可以顯示一個半透明的矩形選擇覆蓋框。這個特性是默認關閉的,可以通過在控件上設置 `LVS_EX_DOUBLEBUFFER` 屬性來開啟它。我們的程序在視圖窗口初始化函數 `CWTLCabViewView::Init()` 里完成了這些工作。結果如下圖說示。 ![](https://box.kancloud.cn/2016-01-13_56962fbf292a7.png) 如果半透明覆蓋區域沒有出現,檢查你的系統是否開啟了這個特性: ![](https://box.kancloud.cn/2016-01-13_56962fbf3b8f0.png) ### 按列排序 Windows XP 以及之后的windows操作體統中,一個report 模式的列表視圖控件可以擁有一個選中的列,用一種不同的背景色顯示。這個特性通常用來指出列表按這個列進行了排序,我們的CAB查看器也是這么做的。頭部空間也有兩種樣式,在列的頂端顯示一個向上或者向下的箭頭。這個通常用來顯示排序的方向(從小到大或者從大到小)。 視圖窗口通過響應 `LVN_COLUMNCLICK` 消息進行排序操作。下面用黑體高亮顯示的代碼用來按列排序。 ``` LRESULT CWTLCabViewView::OnColumnClick ( NMHDR* phdr ) { int nCol = ((NMLISTVIEW*) phdr)-&gt;iSubItem; // If the user clicked the column that is already sorted, // reverse the sort direction. Otherwise, go back to // ascending order. if ( nCol == m_nSortedCol ) m_bSortAscending = !m_bSortAscending; else m_bSortAscending = true; if ( g_bXPOrLater ) { HDITEM hdi = { HDI_FORMAT }; CHeaderCtrl wndHdr = GetHeader(); // Remove the sort arrow indicator from the // previously-sorted column. if ( -1 != m_nSortedCol ) { wndHdr.GetItem ( m_nSortedCol, &hdi ); hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP); wndHdr.SetItem ( m_nSortedCol, &hdi ); } // Add the sort arrow to the new sorted column. hdi.mask = HDI_FORMAT; wndHdr.GetItem ( nCol, &hdi ); hdi.fmt |= m_bSortAscending ?HDF_SORTUP : HDF_SORTDOWN; wndHdr.SetItem ( nCol, &hdi ); } // Store the column being sorted, and do the sort m_nSortedCol = nCol; SortItems ( SortCallback, (LPARAM)(DWORD_PTR) this ); // Indicate the sorted column. if ( g_bXPOrLater ) SetSelectedColumn ( nCol ); return 0; } ``` 第一部分的高亮代碼移除之前用作排序的列頭部的箭頭。如果之前沒有列做為排序的依據,這一步被跳過。接下來,在用戶單擊過的列的頂端添加箭頭。如果按升序排列則肩頭向上,按降序排列箭頭向下。排序完成之后,我們調用 `SetSelectedColumn()` 方法,它是 `LVM_SETSELECTEDCOLUMN` 消息的一個包裝,用來將我們排序的列設置為選中狀態。 按文件大小排序的情況如下圖所示: ![](https://box.kancloud.cn/2016-01-13_56962fbf48e84.gif) ### 使用平鋪視圖模式 在Windows XP以及后續的windows操作系統中,列表視圖空間有一種顯得樣式叫做 _平鋪視圖模式_. 做為視圖窗口初始化的一部分,如果程序運行在XP級后續版本的系統上,會設置視圖列表模式為平鋪視圖模式。 使用了 `SetView()` 方法(它是對 `LVM_SETVIEW` 消息的一個封裝)。然后填充一個 `LVTILEVIEWINFO` 結構來設置空間的一些屬性控制平鋪過程。成員變量 `cLines` 被設置為2,在每個平鋪視圖圖標的旁邊顯示兩行文本。成員變量 `dwFlags` 被設置為 `LVTVIF_AUTOSIZE`,使得控件能夠自動縮放平鋪區域。 ``` void CWTLCabViewView::Init() { // ... // On XP, set some additional properties of the list ctrl. if ( g_bXPOrLater ) { // Turning on LVS_EX_DOUBLEBUFFER also enables the // transparent selection marquee. SetExtendedListViewStyle ( LVS_EX_DOUBLEBUFFER, LVS_EX_DOUBLEBUFFER ); // Default to tile view. SetView ( LV_VIEW_TILE ); // Each tile will have 2 additional lines (3 lines total). LVTILEVIEWINFO lvtvi = { sizeof(LVTILEVIEWINFO), LVTVIM_COLUMNS }; lvtvi.cLines = 2; lvtvi.dwFlags = LVTVIF_AUTOSIZE; SetTileViewInfo ( &lvtvi ); } } ``` #### 設置平鋪視圖圖像列表 對于平鋪視圖模式來說,我們使用了一個特大的系統圖片列表 (默認顯示設置下有 48x48 個圖標 )。我們使用了 `SHGetImageList()` API來獲取這個圖片列表。`SHGetImageList()` 不同于 `SHGetFileInfo()`,它返回一個圖片列表對象上的COM接口。視圖窗口有兩個成員變量用來管理這個圖片列表: ``` CImageList m_imlTiles; // the image list handle CComPtr&lt;IImageList&gt; m_TileIml; // COM interface on the image list ``` 視圖窗口將這個特大圖片列表保存在 `InitImageLists()`里: ``` HRESULT (WINAPI* pfnGetImageList)(int, REFIID, void); HMODULE hmod = GetModuleHandle ( _T("shell32") ); (FARPROC&) pfnGetImageList = GetProcAddress(hmod, "SHGetImageList"); hr = pfnGetImageList ( SHIL_EXTRALARGE, IID_IImageList, (void) &m_TileIml ); if ( SUCCEEDED(hr) ) { // HIMAGELIST and IImageList* are interchangeable, // so this cast is OK. m_imlTiles = (HIMAGELIST)(IImageList*) m_TileIml; } ``` 如果 `SHGetImageList()` 操作成功,我們可以強制轉換 `IImageList*` 接口為 `HIMAGELIST` 類型,然后像其他圖片列表一樣使用它。 #### 使用平鋪視圖圖片列表 因為列表控件沒有為平鋪視圖模式生成一個單獨的圖片列表,我們需要當用戶切換顯示模式時動態改變視圖列表。視圖類有一個 `SetViewMode()` 方法,它用來處理切換視圖列表和查看模式: ``` void CWTLCabViewView::SetViewMode ( int nMode ) { if ( g_bXPOrLater ) { if ( LV_VIEW_TILE == nMode ) SetImageList ( m_imlTiles, LVSIL_NORMAL ); else SetImageList ( m_imlLarge, LVSIL_NORMAL ); SetView ( nMode ); } else { // omitted - no image list changing necessary on // pre-XP, just modify window styles } } ``` 如果空間進入視圖模式,我們設置控件的列表為48x48的那一個圖片列表,否則設置為32x32的那個。 #### 設置而外的幾行文字 初始化過程中,我們建立平鋪視圖來顯示額外的兩行文本。第一行文本是項目名稱,這一點和在大圖標/小圖標模式下一樣。額外的兩行顯示的是子項內容,和report模式下的列接近。我們可以為每個項單獨設置子項文本。下列代碼說明了視圖如何使用 `AddFile()`方法設置文本: ``` // Add a new list item. int nIdx; nIdx = InsertItem ( GetItemCount(), szFilename, info.iIcon ); SetItemText ( nIdx, 1, info.szTypeName ); SetItemText ( nIdx, 2, szSize ); SetItemText ( nIdx, 3, sDateTime ); SetItemText ( nIdx, 4, sAttrs ); // On XP+, set up the additional tile view text for the item. if ( g_bXPOrLater ) { UINT aCols[] = { 1, 2 }; LVTILEINFO lvti = { sizeof(LVTILEINFO), nIdx, countof(aCols), aCols }; SetTileInfo ( &lvti ); } ``` `aCols` 數組包含了要顯示的子項的數據,在這個例子中子項一是文件類型,子項二是文件大小。查看器如下圖所示: ![](https://box.kancloud.cn/2016-01-13_56962fbf58f43.gif) 注意,在你按列排序列表之后這兩行文本的內容會相應改變。當選中的列擁有 `LVM_SETSELECTEDCOLUMN` 樣式的時候,子項的文本總是優先顯示,覆蓋了我們在 `LVTILEINFO` 結構中傳遞的子項文本。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看