“选择文件夹”对话框的封装

本文介绍了一种简化文件夹选择对话框的操作方法,通过封装API实现一键获取用户选择的文件夹路径。提供了两种实现方式,包括直接使用API函数GetPath和通过CFolderDialog类进行更灵活的配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们经常需要用到“选择文件夹”对话框,相应的API已经很好用,但稍嫌麻烦,
所以我专门将其封装了一下,力求一步到位。


函数封装如下:
/*****************************************************************
** 函数名:GetPath
** 输 入: 无
** 输 出: CString strPath
**        strPath非空, 表示用户选择的文件夹路径
**        strPath为空, 表示用户点击了“取消”键,取消选择
** 功能描述:显示“选择文件夹”对话框,让用户选择文件夹
****************************************************************/


CString GetPath()
{
 CString strPath = "";
 BROWSEINFO bInfo;
 ZeroMemory(&bInfo, sizeof(bInfo));
 bInfo.hwndOwner = m_hWnd;
 bInfo.lpszTitle = _T("请选择路径: ");
 bInfo.ulFlags = BIF_RETURNONLYFSDIRS;    
 
 LPITEMIDLIST lpDlist; //用来保存返回信息的IDList
 lpDlist = SHBrowseForFolder(&bInfo) ; //显示选择对话框
 if(lpDlist != NULL)  //用户按了确定按钮
 {
  TCHAR chPath[255]; //用来存储路径的字符串
  SHGetPathFromIDList(lpDlist, chPath);//把项目标识列表转化成字符串
  strPath = chPath; //将TCHAR类型的字符串转换为CString类型的字符串
 }
 return strPath;
}


调用时只需要用到以下代码:
CString strPath = GetPath();
则strPath为用户选择的文件夹路径。如果用户点击了对话框的取消键,则strPath为空字符串("");


//-----------------------------------------------------


FolderDialog.h


C/C++ code#ifndef FolderDialog_H_
#define FolderDialog_H_




class CFolderDialog
{
public:
    CFolderDialog(int rootDirFlag = CSIDL_DESKTOP,  \
        const CString &focusDir = _T(""),  \
        const CString &title = _T(""),  \
        DWORD browseInfoFlag = 0);


    int DoModal();


    inline const CString &GetPath()const
    {
        return finalPath_;
    }
    
private:
    const int rootDirFlag_;
    const CString focusDir_;
    const CString title_;
    const DWORD browseInfoFlag_;
    
    HWND hDialog_;
    CString finalPath_;
    TCHAR tmpPath_[MAX_PATH];
    
    friend int CALLBACK BrowseDirectoryCallback(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData);
};

#endif // FolderDialog_H_


FolderDialog.cpp
C/C++ code#include "stdafx.h"
#include "FolderDlg.h"

static int CALLBACK BrowseDirectoryCallback(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
    ASSERT(hWnd != NULL);

    CFolderDialog *lpDialog = ((CFolderDialog*)lpData);

    if (uMsg == BFFM_INITIALIZED)
    {
        lstrcpy(lpDialog->tmpPath_, lpDialog->focusDir_);
        ::SendMessage(hWnd, BFFM_SETSELECTION, TRUE, (LPARAM)lpDialog->tmpPath_);
    }
    return 0;
}

//-------------------------------------------------------------------------
//
//-------------------------------------------------------------------------

CFolderDialog::CFolderDialog(int rootDirFlag, \
                             const CString &focusDir, \
                             const CString &title, \
                             DWORD browseInfoFlag): \
                             rootDirFlag_(rootDirFlag), \
                             focusDir_(focusDir), \
                             title_(title), \
                             browseInfoFlag_(browseInfoFlag), \
                             hDialog_(NULL), \
                             finalPath_(_T(""))\
{
    ::memset(tmpPath_, 0, sizeof(tmpPath_));
}

int CFolderDialog::DoModal()
{
    LPITEMIDLIST rootLoation;
    SHGetSpecialFolderLocation(NULL, rootDirFlag_, &rootLoation);
    if (rootLoation == NULL)
    {
        throw new CException();
    }
    
    BROWSEINFO browseInfo;
    ZeroMemory(&browseInfo, sizeof(browseInfo));
    browseInfo.pidlRoot = rootLoation;
    browseInfo.ulFlags = browseInfoFlag_;
    browseInfo.lpszTitle = title_;
    browseInfo.lpfn = BrowseDirectoryCallback;
    browseInfo.lParam = (LPARAM)this;
    
    LPITEMIDLIST targetLocation = SHBrowseForFolder(&browseInfo);
    if (targetLocation == NULL)
    {
        return IDCANCEL;
    }
    
    TCHAR targetPath[MAX_PATH] = {_T('\0')};
    SHGetPathFromIDList(targetLocation, targetPath);
    finalPath_ = targetPath;
    
    return IDOK;
}

我前两天刚用的..加入工程..直接调用..可以指定初始目录

CFileDialog fileDialog(TRUE,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,
L"Key文件 (*.afpkey)|*.afpkey||");

if(IDOK!=fileDialog.DoModal())
{
return ;
}

Assembly codeinclude     ole32.inc
includelib  ole32.lib

_szDirInfo  db "temp"
_BrowseFolderCallBack   proc    hWnd,uMsg,lParam,lpData
                        local   @szBuffer[260]:byte
        mov             eax,uMsg
        .if             eax ==  BFFM_INITIALIZED
                        invoke  SendMessage,hWnd,BFFM_SETSELECTION,\
                        TRUE,_BrowseFolderTmp
        .elseif         eax ==  BFFM_SELCHANGED
                        invoke   SHGetPathFromIDList,lParam,addr @szBuffer
                        invoke   SendMessage,hWnd,BFFM_SETSTATUSTEXT,\
                               0,addr @szBuffer
        .endif
        xor             eax,eax
        ret
_BrowseFolderCallBack   endp


BROWSEINFO STRUCT
 HwndOwner             dd ?      ;对话框的父窗口
 PidlRoot              dd ?      ;用来表示起始目录的ITEMIDLIST目录
 PszDisplayName        dd ?      ;用来接收用户选择目录的缓冲区
 LpszTitle             dd ?      ;对话框中的用户定义文字
 ulFlags               dd ?      ;标志
 lpfn                  dd ?      ;回调函数地址
 lParam                dd ?      ;传给回调函数的参数
 iImage                dd ?      ;用来接收选中目录的图像
BROWSEINFO ENDS
Assembly codelocal         lpbi: BROWSEINFO
szPath        db MAX_PATH dup (?)
              mov      lpbi.lpfn,offset _BrowseFolderCallBack
              mov      lpbi.lpszTitle,offset _szDirInfo
              mov      lpbi.ulFlags,\
                       BIF_RETURNONLYFSDIRS or BIF_STATUSTEXT


invoke        SHBrowseForFolder,lpbi ;返回一个ITEMIDLIST结构指针
invoke        SHGetPathFromIDList,lpItemIDList,addr szPath;转换ITEMIDLIST结构为全目录的字符串


不翻译了,罗云逼汇编中的这段大量宏的asm应该很简单。自己使用时需初始化COM以及释放所占用的COM


● ulFlags——用来定义对话框类型的标志,下面是一些重要的标志:
■ BIF_BROWSEFORPRINTER——对话框中只能选择打印机。
■ BIF_BROWSEINCLUDEFILES——同时显示目录中的文件。
■ BIF_RETURNONLYFSDIRS——只返回文件系统中的目录。
■ BIF_STATUSTEXT——对话框中显示一个状态栏。
■ BIF_EDITBOX——显示一个编辑框供用户手工输入目录。
■ BIF_VALIDATE——显示编辑框的时候检测用户输入目录的合法性。


当函数执行后,将显示对话框,当对话框初始化以及每当用户选择不同的目录的时候,函数调用lpfn指定的回调函数,回调函数的参数有4个,分别是父窗口句柄hWnd、消息类型uMsg、消息参数lParam和自定义数据lpData
回调函数可能收到的消息有3种:
● BFFM_INITIALIZED——在对话框初始化的时候收到。
● BFFM_SELCHANGED——在用户选择了一个目录的时候收到,这时lParam参数指向一个表示当前被选择目录的ITEMIDLIST结构。

● BFFM_VALIDATEFAILED——用户输入了一个不合法的目录名。





<think>我们正在使用Vue框架,需要实现点击按钮弹出文件夹选择对话框的功能。根据提供的引用,有两种主要方法:1.使用Electron的dialog.showOpenDialog(引用[1])-但这种方法适用于Electron桌面应用环境。2.使用隐藏的input元素(引用[2])-这种方法在纯Web环境中可行,但需要注意的是,inputtype="file"允许选择文件,而选择文件夹则需要添加`webkitdirectory`属性(非标准,但现代浏览器支持)。由于用户没有明确说明是Electron环境还是Web环境,我们考虑两种场景。场景1:如果是Electron项目(桌面应用)可以使用Electron的dialog模块,特别是dialog.showOpenDialog方法,它可以返回一个Promise,然后处理结果。场景2:如果是Web项目(浏览器环境)使用隐藏的input元素,并设置`webkitdirectory`属性,这样用户就只能选择文件夹。注意这是非标准属性,但主流浏览器(Chrome,Edge,Firefox等)支持。根据引用[2]中的方法,我们可以稍作修改以实现选择文件夹。在Vue中,我们可以这样实现:步骤:1.在模板中添加一个隐藏的input元素,类型为file,并添加`webkitdirectory`属性(以及可能需要的`directory`属性以兼容某些旧版),并监听change事件。2.在按钮的点击事件中触发这个input的点击事件。注意:由于浏览器的安全限制,我们不能直接通过JavaScript设置`value`属性,但可以触发点击事件。下面我们分别提供两种环境的代码示例。假设用户是在Web环境下(因为问题中没有提到Electron),我们将重点放在Web环境的实现上。代码实现(Web环境):在Vue组件的模板中:<inputref="folderInput"type="file"style="display:none"webkitdirectory@change="onFolderSelected"/><button@click="openFolderDialog">选择文件夹</button>在Vue组件的methods中:methods:{openFolderDialog(){//通过ref获取input元素并触发点击this.$refs.folderInput.click();},onFolderSelected(event){//当用户选择文件夹后,event.target.files会包含选择文件文件夹中的所有文件)//注意:虽然我们选择的是文件夹,但浏览器会将文件夹内的所有文件列出来//我们可以通过file.webkitRelativePath来获取文件的相对路径,从而知道文件夹结构constfiles=event.target.files;if(files.length){//我们可以取第一个文件,然后根据其webkitRelativePath得到文件夹路径//例如:文件夹名是'documents',第一个文件路径是'documents/file.txt'//那么文件夹路径就是第一个文件的webkitRelativePath.split('/')[0]//但是注意,这里得到的是所有文件,我们可能需要的是文件夹路径//实际上,整个文件列表中的每个文件的webkitRelativePath都是以'文件夹名/'开头的//所以我们无法直接获取用户选择文件夹的完整路径(由于安全原因),但是可以知道文件夹名。//另外,在最新的浏览器中,通过event.target.files可以获取到文件夹路径吗?答案是不可以,因为安全原因,路径被省略了,只有文件夹名。//所以,如果我们只需要文件夹名,可以通过上述方法。如果我们需要完整路径,在Web环境下是无法直接获取的。//但是,我们可以通过webkitRelativePath提取出共同的目录前缀(即用户选择文件夹路径)//注意:如果用户只选了一个文件夹,那么所有文件的webkitRelativePath应该都有相同的文件夹名作为第一级。//然而,由于安全策略,我们只能获取到文件名和webkitRelativePath(相对路径),不能得到完整路径。//所以,实际应用中,我们通常只能得到文件夹内的文件列表和相对路径,而得不到用户系统上的绝对路径。//如果我们想得到用户选择文件夹名称,可以通过第一个文件的webkitRelativePath分割得到:constfolderName=files[0].webkitRelativePath.split('/')[0];console.log('选择文件夹名称是:',folderName);//或者,我们可以将整个文件列表进行处理,以遍历文件夹中的所有文件。}}}注意:在Web环境下,由于安全原因,我们不能获取到用户选择文件夹的完整路径(绝对路径),只能获取到文件夹内的文件列表和每个文件的相对路径(相对于该文件夹)。另外,如果用户使用的是Electron,则可以获取完整的文件夹路径:代码实现(Electron环境):假设在Vue组件的模板中:<button@click="openFolderDialog">选择文件夹</button>在Vue组件的methods中:const{dialog}=require('electron').remote;//如果在渲染进程中使用//或者在主进程与渲染进程之间使用IPC,但通常可以直接在渲染进程这样调用(有安全提示,建议使用主进程)methods:{openFolderDialog(){dialog.showOpenDialog({properties:['openDirectory']}).then(({canceled,filePaths})=>{if(!canceled){constfolderPath=filePaths[0];console.log('选择文件夹路径是:',folderPath);//这里可以得到完整路径}});}}但是,由于Electron的安全设置,可能需要设置nodeIntegration为true(不推荐)或者使用preload脚本注入。另外,在Vue中,一般会搭配vue-cli-plugin-electron-builder来使用,确保正确集成Electron。考虑到用户需求是在Vue中实现,但没有明确环境,我们可以先假设是Web环境,然后如果是Electron环境再给出备选方案。总结:-Web环境:使用input[type=file]和webkitdirectory属性,注意只能获取文件夹内的文件列表和相对路径,不能得到绝对路径。-Electron环境:使用dialog.showOpenDialog,可以获取到文件夹的绝对路径。根据引用[2]和[1]的方法,我们提供了两种解决方案。下面,我们给出完整的Vue组件示例(Web环境):</think> 在 Vue 中实现点击按钮弹出文件夹选择对话框的功能,有以下两种常用方法: ### ⚡ 方法一:使用隐藏的 `<input type="file">`(Web通用方案) ```vue <template> <div> <!-- 隐藏的文件选择输入框 --> <input ref="folderInput" type="file" style="display: none" webkitdirectory @change="handleFolderSelect" > <!-- 触发按钮 --> <el-button @click="triggerFolderDialog"> 选择文件夹 </el-button> </div> </template> <script> export default { methods: { // 触发文件夹对话框 triggerFolderDialog() { this.$refs.folderInput.click(); // 通过编程方式触发点击 }, // 处理文件夹选择结果 handleFolderSelect(event) { const files = event.target.files; if (!files.length) return; // 获取文件夹路径(Chrome/Edge支持) const folderPath = files[0].webkitRelativePath.split('/')[0]; console.log('选择文件夹:', folderPath); // 处理文件夹内的所有文件 (file对象列表) console.log('文件夹文件:', Array.from(files)); } } } </script> ``` #### 💡 特点: 1. 添加 `webkitdirectory` 属性启用文件夹选择模式 2. 获取的 `files` 包含文件夹内所有文件的 `File` 对象 3. 通过 `webkitRelativePath` 可提取相对路径 4. **浏览器兼容性**:支持 Chrome、Edge、Firefox(需在 `about:config` 启用 `dom.input.dirpicker`) ### ⚡ 方法二:使用 Electron 对话框(仅限 Electron 应用) ```vue <template> <el-button @click="openFolderDialog"> Electron选择文件夹 </el-button> </template> <script> export default { methods: { async openFolderDialog() { const { dialog } = require('electron').remote; const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openDirectory'] }); if (!canceled && filePaths.length > 0) { console.log('文件夹路径:', filePaths[0]); // 调用后续处理逻辑 } } } } </script> ``` #### 💡 特点: 1. 完整文件系统路径访问权限 2. 提供原生系统对话框体验 3. **仅限** Electron 应用环境 ### 📌 实践建议 1. **Web应用** → 选择方法一(使用 `webkitdirectory`) 2. **Electron应用** → 选择方法二(调用 `dialog.showOpenDialog()`) 3. **路径获取差异**: - Web:只能获取相对路径文件列表 - Electron:可获取绝对系统路径 4. **安全限制**:浏览器环境无法直接获取完整物理路径[^2][^1] ### 📎 示例组件封装(Web版) ```vue <template> <div> <input ref="hiddenInput" type="file" hidden webkitdirectory @change="onFolderSelected" > <slot name="trigger" :open="triggerDialog"></slot> </div> </template> <script> export default { methods: { triggerDialog() { this.$refs.hiddenInput.value = null; // 重置选择 this.$refs.hiddenInput.click(); }, onFolderSelected(e) { const files = e.target.files; if (!files.length) return; this.$emit('select', { folderName: files[0].webkitRelativePath.split('/')[0], files: Array.from(files) }); } } } </script> ``` 用法: ```vue <FolderPicker @select="handleFolder"> <template #trigger="{ open }"> <el-button @click="open"> 选择文件夹 </el-button> </template> </FolderPicker> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值