【MFC】BROWSEINFO設定路徑,支援記憶上次路徑
01、目錄
02、BROWSEINFO結構
BROWSEINFOW
結構是一種顯示檔案或者資料夾路徑的結構體。作為SHBrowseForFolder()
函式的引數。
首先看下BROWSEINFO
的原型(來自微軟官方文件):
typedef struct _browseinfoW {
HWND hwndOwner;
PCIDLIST_ABSOLUTE pidlRoot;
LPWSTR pszDisplayName;
LPCWSTR lpszTitle;
UINT ulFlags;
BFFCALLBACK lpfn;
LPARAM lParam;
int iImage;
} BROWSEINFOW, *PBROWSEINFOW, *LPBROWSEINFOW;
引數介紹:
hwndOwner
:父視窗控制程式碼,通常通過AfxGetMainWnd()->GetSafeHwnd()
函式獲取安全的控制程式碼。pidlRoot
:顯示的檔案目錄對話方塊的根(Root),一般設定為NULL
。pszDisplayName
:儲存被選取的資料夾路徑的緩衝區。lpszTitle
:顯示位於對話方塊左上部的標題。ulFlags
:指定對話方塊的外觀和功能的標誌。(下面會聊此引數的作用)lpfn
:處理事件的回撥函式,一般設定為NULL
。lparam
:應用程式傳給回撥函式的引數,一般設定為NULL
。iImage
:資料夾對話方塊的圖片索引,一般設定為0。
說明:
一般而言父視窗控制程式碼(hwndOwner
)和根(pidlRoot
)設定為Null就可以了,pszDisplayName
設定一塊MAX_PATH
大小的緩衝區,跟顯示相關的引數就是對話方塊提示標題(lpszTitle
)、對話方塊樣式(ulFlags
)、設定對話方塊的預設路徑的操作(lpfn
和lParam
)以及對話方塊工作列上顯示的圖示(iImage
)。
由於返回值LPITEMIDLIST
是一個指向ITEMIDLIST
的指標,這個ITEMIDLIST
涉及到Windows Shell中關於管理諸如檔案、網路上的計算機、控制皮膚程式、回收站等等物件的知識點,Windows Shell為了識別具體的每一個物件,就使用了ITEMID
來唯一識別和區分,而ITEMIDLIST
就是一個完整的物件路徑。顯然這個函式可以用來瀏覽非檔案物件,比如區域網內的電腦等等,在這裡這個LPITEMIDLIST
返回的物件路徑是一個資料夾的路徑,Windows提供了一個函式BOOL SHGetPathFromIDList(LPCITEMIDLIST pidl, LPSTR pszPath)
來實現從物件路徑轉化為資料夾路徑。
03、淺談Windows回撥函式
MFC的回撥函式機制,沒記錯前面應該聊過(不管有沒有聊過,這裡再說說)。
MFC中應該有兩類回撥函式:一類是源自C的傳統回撥函式,此類回撥函式若非定義為全域性函式,而定義在類中的話,要新增static約束,常見的有EnumXXX();一類是訊息響應函式,通過成員函式指標實現回撥。
設想一種情況,基類A觸發某事件E後,回撥某定義好的函式F進行事件處理(MFC中表現為訊息響應函式)。繼承於類A的子類B和C,可能對於E有不同的處理方式,於是需要對基類A的函式F進行改寫。自然而然的,我們想到將F定義為以virtual修飾的虛擬函式。
回撥函式的簡單定義就是你定義的由Windows來呼叫。以下兩個函式摘自《Programming Windows with MFC》,這裡暫且不管函式的具體作用,在FillListBox
中有一個API函式,它呼叫的回撥函式是EnumFontFamProc
,回撥函式的宣告形式一般都是相對固定的,具體可以參考MSDN。
static int CALLBACK EnumFontFamProc (ENUMLOGFONT* lpelf,NEWTEXTMETRIC* lpntm, int nFontType, LPARAM lParam);
void CMainWindow::FillListBox ()
{
m_wndListBox.ResetContent ();
CClientDC dc (this);
::EnumFontFamilies ((HDC) dc, NULL, (FONTENUMPROC) EnumFontFamProc,(LPARAM) this);
}
int CALLBACK CMainWindow::EnumFontFamProc (ENUMLOGFONT* lpelf,NEWTEXTMETRIC* lpntm, int nFontType, LPARAM lParam)
{
CMainWindow* pWnd = (CMainWindow*) lParam;
if ((pWnd->m_wndCheckBox.GetCheck () == BST_UNCHECKED) || (nFontType & TRUETYPE_FONTTYPE))
pWnd->m_wndListBox.AddString (lpelf->elfLogFont.lfFaceName);
return 1;
}
請注意這裡的函式EnumFontFamilies中的最後一個引數傳遞的是this即該CMainWindow物件的指標,為什麼要這樣呢,可以看到EnumFontFamProc的宣告是static,在C++中static函式是不能呼叫非static成員的,所以這裡傳遞一個this就不是很奇怪了。
但是為什麼要將該函式宣告為static呢,這就要歸咎於C++的特殊性了,眾所周知C++編譯器在編譯的時候都會在物件中新增一個this指標,在成員函式呼叫中又會附加一個引數儲存this指標,但是Windows的回撥函式有嚴格的定義就是必須按照引數列表傳遞的引數,加了this指標後引數列表就會與Windows期望的引數列表不一致了,因此這裡將其宣告為static
(static成員函式不會傳遞this指標,這點說起來總是知道,但是真正用時總是忘了,唉)。
另外在Windows中使用callback函式很常見,恰好許多支援回撥函式的API函式都像這裡的EnumFontFamilies一樣支援自定義的LPARAM引數,剛好可以傳遞this,如果使用的API函式不支援這樣的自定義的LPARAM引數,就需要其他的方法了,一種比較簡單的方法是將this複製為global變數使得回撥函式可以使用。
04、 BROWSEINFO回撥函式
BROWSEINFO
結構的回撥函式如果不在本類寫的話,預設是跟隨自afxshellmanager.h
中,如下所示:
static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lparam, LPARAM lpData);
我們則要重新在本類寫此回撥,使用本類的回撥函式。
舉例:
//.h中宣告靜態成員方法
static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lparam, LPARAM lpData);
//.cpp中寫實現
int XXX::BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lparam, LPARAM lpData)
{
if(uMsg == BFFM_INITIALIZED)
{
CTreeCtrl treePath;
HTREEITEM hItemSel;
::SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData); //傳送訊息給父視窗
//下面的0x3741是一塊沒人用的地址,我試過,如果地址有人用就會錯,這裡找塊兒空地址即可
treePath.SubclassWindow(::GetDlgItem(hwnd,0x3741));
hItemSel = treePath.GetSelectedItem(); //選擇當前選擇的節點
treePath.Expand(hItemSel, TVE_COLLAPSE); //樹擴充套件,具體幹啥,我也不太清楚
treePath.UnsubclassWindow(); //視窗子類化,為了擷取Windows的訊息
}
return 0;
}
瞭解了回撥函式,接下來就看兩個例子,熟悉下本節的內容吧!
05、應用例項
功能: 建立一個應用程式,使點選按鈕可以生成對話方塊,提供選擇資料夾/檔案的功能。
支援: 如果顯示路徑控制元件支援儲存、載入功能,則此程式可以實現記憶上次選擇資料夾/檔案路徑的功能,如果不支援,就只能在不關軟體的前提下支援此功能。
int CCreatePipeDlg::BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lparam, LPARAM lpData)
{
if(uMsg == BFFM_INITIALIZED)
{
CTreeCtrl treePath;
HTREEITEM hItemSel;
::SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
treePath.SubclassWindow(::GetDlgItem(hwnd,0x3741));
hItemSel = treePath.GetSelectedItem();
treePath.Expand(hItemSel, TVE_COLLAPSE);
treePath.UnsubclassWindow();
}
return 0;
}
//按鈕的訊息處理函式
void CCreatePipeDlg::OnBnClickedBtnLoad()
{
//TODO:在此新增控制元件通知處理程式程式碼
BROWSEINFO bi;
char szPath[MAX_PATH];
LPITEMIDLIST pList = NULL;
ZeroMemory(szPath, MAX_PATH);
//獲取當前路徑
GetDlgItemText(ID_EDIT_PATH, (LPWSTR)szPath, MAX_PATH);
//配置路徑對話方塊
memset(&bi, 0, sizeof(BROWSEINFO));
bi.hwndOwner = AfxGetMainWnd()->GetSafeHwnd();
bi.pidlRoot = NULL;
bi.pszDisplayName = (LPWSTR)szPath;
//這裡切記不能用:(LPWSTR)"選擇資料夾",不然會出現亂碼
bi.lpszTitle = _T("選擇資料夾");
bi.ulFlags = BIF_EDITBOX;
bi.lpfn = BrowseCallbackProc;
bi.lparam = (LPWSTR)szPath;
bi.iImage = 0;
//彈出選擇資料夾/檔案對話方塊
if((pList = SHBrowseForFolder(&bi)) != NULL)
{
if(SHGetPathFromIDList(pList, (LPWSTR)szPath))
{
//設定路徑
SetDlgItemText(IDC_EDIT_PATH, (LPWSTR)szPath);
}
}
}
下面優化一下上面的程式碼,改變一下引數即可實現支援新建資料夾功能!
int CCreatePipeDlg::BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lparam, LPARAM lpData)
{
if(uMsg == BFFM_INITIALIZED)
{
CTreeCtrl treePath;
HTREEITEM hItemSel;
::SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
treePath.SubclassWindow(::GetDlgItem(hwnd,0x3741));
hItemSel = treePath.GetSelectedItem();
treePath.Expand(hItemSel, TVE_COLLAPSE);
treePath.UnsubclassWindow();
}
return 0;
}
//按鈕的訊息處理函式
void CCreatePipeDlg::OnBnClickedBtnLoad()
{
//TODO:在此新增控制元件通知處理程式程式碼
BROWSEINFO bi;
char szPath[MAX_PATH];
LPITEMIDLIST pList = NULL;
ZeroMemory(szPath, MAX_PATH);
//獲取當前路徑
GetDlgItemText(ID_EDIT_PATH, (LPWSTR)szPath, MAX_PATH);
//配置路徑對話方塊
memset(&bi, 0, sizeof(BROWSEINFO));
bi.hwndOwner = AfxGetMainWnd()->GetSafeHwnd();
bi.pidlRoot = NULL;
bi.pszDisplayName = (LPWSTR)szPath;
//這裡切記不能用:(LPWSTR)"選擇資料夾",不然會出現亂碼
bi.lpszTitle = _T("選擇資料夾");
bi.ulFlags = BIF_EDITBOX | BIF_NEWDIALOGSTYLE; //此處增加BIF_NEWDIALOGSTYLE即可
bi.lpfn = BrowseCallbackProc;
bi.lparam = (LPWSTR)szPath;
bi.iImage = 0;
//彈出選擇資料夾/檔案對話方塊
if((pList = SHBrowseForFolder(&bi)) != NULL)
{
if(SHGetPathFromIDList(pList, (LPWSTR)szPath))
{
//設定路徑
SetDlgItemText(IDC_EDIT_PATH, (LPWSTR)szPath);
}
}
}
如果使用的地方多,可以將BIF_EDITBOX | BIF_NEWDIALOGSTYLE
定義為全域性巨集,如下:
#define NEWFILE_MEMROY BIF_EDITBOX | BIF_NEWDIALOGSTYLE //全域性巨集
然後程式中:
bi.ulFlags = NEWFILE_MEMROY BIF_EDITBOX;
06、小結
弄清楚每個引數的意義,然後才能按需求設定初值。先太盲目了,只知道套別人的,根本就沒有理解。
如果連每一行在幹什麼都不知道,就知道copy程式碼的話,變一下花樣也許就失去了方向。
此次感觸最深莫過於此,希望以後引以為鑑,努力修煉!
版權宣告:轉載請註明出處,謝謝!
相關文章
- Qt 記住上次開啟路徑QT
- 怎麼設定jupyter路徑?
- linux上java路徑設定LinuxJava
- 設定 maven jetty的根路徑MavenJetty
- ThinkPhP 路徑定義PHP
- Xcode外掛路徑、快取路徑、圖片壓縮工具路徑、程式碼片段路徑、symbolicatecrash路徑XCode快取Symbol
- 設定oracle資料字典的路徑Oracle
- IP_別名、路徑_別名、路徑_wwid及開機啟動設定
- java配置檔案設定絕對路徑Java
- 為WinDbg設定符號檔案路徑符號
- canvas 路徑與子路徑Canvas
- html中的路徑的介紹:絕對路徑和相對路徑HTML
- Linux檔案的路徑定位-相對路徑和絕對路徑Linux
- HTML絕對路徑與相對路徑HTML
- Golang os 包與設定配置檔案路徑Golang
- linux ubuntu終端路徑顯示設定LinuxUbuntu
- Linux小知識翻譯-「路徑設定」Linux
- php的open_basedir設定多個路徑PHP
- 尤拉路徑
- 檔案的相對路徑和絕對路徑以及根相對路徑
- struts/Servlet,action轉到jsp後,路徑問題(struts2,jsp路徑,action路徑,action跳轉,相對路徑,絕對路徑)...ServletJS
- xpath路徑表示式筆記筆記
- 尤拉路徑學習筆記筆記
- JBoss應用根路徑定義
- 檔案絕對路徑和相對路徑
- Java工程路徑及相對路徑(轉載)Java
- Jsp相對路徑和絕對路徑JS
- 設定 Windows Terminal 中 Ubuntu 預設開啟路徑WindowsUbuntu
- 設定手機郵件下載檔案路徑
- 怎麼設定spyder檔案的儲存路徑?
- oracle歸檔日誌儲存路徑的設定Oracle
- 請教,如何在JBOSS中設定虛擬路徑?
- JAVA 取得當前目錄的路徑/Servlet/class/檔案路徑/web路徑/url地址JavaServletWeb
- canvas路徑與子路徑詳解Canvas
- 深入解析Java絕對路徑與相對路徑Java
- nodejs路徑處理方法和絕對路徑NodeJS
- 絕對路徑和相對路徑的區別,
- javascript將相對路徑修改為絕對路徑JavaScript