当前位置:C++技术网 > 资讯 > 定制文件夹选择对话框的样式和大小实现方法

定制文件夹选择对话框的样式和大小实现方法

更新时间:2015-06-26 21:42:07浏览次数:1+次

    前面讲过了两种风格的文件夹选择对话框的实现方法,见文章《Win7界面的和API实现的老界面文件夹选择对话框代码实现详细讲解》。而老界面的文件夹选择对话框很小,也导致选择很不方便,所以很多人就不喜欢这种对话框了。但是今天讲的定制这种文件夹选择对话框的样式和大小,或许可以大大提升用户体验吧。嘿嘿。
    首先来看看实现截图吧,看图说话,有图有真相!

横向扩大样式
双向扩大样式

纵向扩大样式
    第一张图是横向的,第二张图是纵向的,第三张图是横向纵向都扩大了。对话框中,还增加了编辑框,可以直接输入文件夹路径。选择了文件夹后,会自动更新选中的文件夹路径到编辑框中。看看,效果还不错,下面来看看如何定制,摆脱原始的小小的选择框吧。
    实现的原理,其实很简单,就是提供了一个回调函数来进行定制。为什么回调函数容易实现定制,请看文章《函数调用与回调函数的设计原理的深入对比分析》的详细分析。这里就不对回调函数机制详细讨论了。使用回调函数,就是给你有机会定制界面,所以回调函数是一个非常不错的东西哦。而在回调函数中,是对消息BFFM_INITIALIZED进行响应,然后对文件夹选择对话框中的所有子控件进行遍历,然后调整大小和位置,进而实现定制。
    下面看看实现的代码:
    要使用回调函数定制,那么就要提供回调函数,在文件夹选择对话框的结构体中的成员lpfn传递回调函数地址即可。启动文件夹选择对话框的代码如下:

const TCHAR* lpPath=_T("C:\\Program Files\\");
TCHAR lpTemp[MAX_PATH]={0};// - 存储选中的文件夹路径
BROWSEINFO bi;
memset(&bi, 0, sizeof(bi));
LPITEMIDLIST pItemList;
bi.hwndOwner = m_hWnd;// - 设置拥有者窗口句柄,如果为NULL,相当于非模态窗口
bi.lpszTitle = _T("★C++技术网 提醒★ ?请选择文件夹?");// - 选择文件夹中的提示文字
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX;
bi.lpfn = BrowseCallbackProc;// - 回调函数,用于定制文件夹选择对话框
bi.lParam=(LPARAM)lpPath;// - 传入默认选中的文件夹
pItemList = SHBrowseForFolder(&bi);
if (pItemList!=NULL)
{
	SHGetPathFromIDList(pItemList,lpTemp);// - 得到选中的文件夹路径
}

代码说明:
    代码中的注释已经写的很清楚了,每一步基本都标清楚了,所以不多解释了。要说一下的就是BROWSEINFO 结构体。此结构体用于对文件夹选择对话框传递各种参数,包括定制界面的回调函数。结构体的成员ulFlags 可以使用多个标识组合起来使用,实现对界面的定制。BIF_EDITBOX就是多出来的编辑框。BIF_RETURNONLYFSDIRS表示只返回文件夹。这些标识有很多,具体的就要自己查MSDN了,里面说的很清楚,我就不重复解释,只是告诉你方法,你要自己动手去查去学习。SHBrowseForFolder就是启动文件夹选择对话框的Shell函数。执行完后,然后判断是否成功,然后调用SHGetPathFromIDList得到文件夹列表中选择的文件夹,存入lpTemp中。
我们要提供一个回调函数,首先要声明这个回调函数,声明如下:
static int __stdcall BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData);
    必须是类静态的,所以要加上static 关键字。其他的格式,都是API规定的格式。在实现回调函数之前,我自定义了一个结构体,用来存储子控件的样式的,存储左右上下边距,宽度和高度。用结构体形式整体存储这些信息,可以简化代码,让逻辑更加清楚。并使用typedef重定义了结构体类型,让使用更加方便。
结构体定义如下:
typedef struct ControlLayout
{
    int iWidth;        // - 宽度
    int iHeight;       // - 高度
    int iMargin_Left;  // - 左边距
    int iMargin_Right; // - 右边距
    int iMargin_Top;   // - 上边距
    int iMargin_Bottom;// - 下边距
}CTRLLAYOUT;


回调函数的代码实现如下:

int CALLBACK CfolderDlg::BrowseCallbackProc(HWND hwnd,UINT uMsg,LPARAM lParam,LPARAM lpData)
{
    CWnd* pMainWnd;
    CRect OrgMainClientRect;// - 调整前文件夹对话框客户区矩形
    CRect NewMainWndRect;// - 调整后文件夹对话框整个窗口矩形
    CWnd* pChildWnd;// - 子窗口指针
    TCHAR szClassName[MAX_PATH];// - 控件Windows窗口类类名

    CRect TreeRect;  // -树控件矩形区域
    CRect ButtonRect;// - 按钮控件矩形区域

    CTRLLAYOUT TreeLayout={0};// - 树控件样式
    CTRLLAYOUT ButtonLayout[2]={0};// - 树控件样式

    int iIndex = 0;// - 对按钮数组计数
    switch (uMsg)
    {
    case BFFM_INITIALIZED:
        pMainWnd = CWnd::FromHandle(hwnd);// - 根据窗口句柄,获取文件夹选择对话框指针
        pMainWnd->GetClientRect(OrgMainClientRect);// - 获取主窗口客户区的大小,即控件放置的区域
        pChildWnd = pMainWnd->GetWindow(GW_CHILD); // - 获取子窗口的指针
        /* - 循环便利文件夹选择对话框的控件,并存储控件的位置信息 - */
        while (pChildWnd != NULL)
        {
            GetClassName(pChildWnd->m_hWnd, szClassName, MAX_PATH);
            if (_tcscmp(_T("SysTreeView32"), szClassName) == 0)
            {
                /* - 存储树控件样式 - */
                pChildWnd->GetWindowRect(TreeRect);
                pMainWnd->ScreenToClient(TreeRect);

                TreeLayout.iMargin_Left = TreeRect.left;
                TreeLayout.iMargin_Top = TreeRect.top;
                TreeLayout.iMargin_Right = OrgMainClientRect.right - TreeRect.right;
                TreeLayout.iMargin_Bottom = OrgMainClientRect.bottom - TreeRect.bottom;
            }
            if (_tcscmp(_T("Button"), szClassName) == 0)
            {
                /* - 存储按钮控件样式 - */
                pChildWnd->GetWindowRect(ButtonRect);
                pMainWnd->ScreenToClient(ButtonRect);
                ButtonLayout[iIndex].iWidth = ButtonRect.Width();
                ButtonLayout[iIndex].iHeight = ButtonRect.Height();
                ButtonLayout[iIndex].iMargin_Right = OrgMainClientRect.right - ButtonRect.right;
                ButtonLayout[iIndex].iMargin_Bottom = OrgMainClientRect.bottom - ButtonRect.bottom;
                iIndex++;
            }
            pChildWnd = pChildWnd->GetNextWindow();
        }
        /* - 增大文件夹对话框和对话框中的客户区 - */
        pMainWnd->GetWindowRect(NewMainWndRect);
        OrgMainClientRect.InflateRect(0, 0, 240, 300);
        NewMainWndRect.InflateRect(0, 0, 240, 300);
        pMainWnd->MoveWindow(NewMainWndRect);

        /* - 修改控件大小和位置 - */
        iIndex = 0;
        pChildWnd = pMainWnd->GetWindow(GW_CHILD);// - 移动主窗口后获取子窗口指针
        while (pChildWnd != NULL)
        {
            GetClassName(pChildWnd->m_hWnd, szClassName, MAX_PATH);
            if (_tcscmp(_T("SysTreeView32"), szClassName) == 0)
            {
                TreeRect.left = TreeLayout.iMargin_Left;
                TreeRect.top = TreeLayout.iMargin_Top;
                TreeRect.right = OrgMainClientRect.right - TreeLayout.iMargin_Right;
                TreeRect.bottom = OrgMainClientRect.bottom - TreeLayout.iMargin_Bottom;
                pChildWnd->MoveWindow(TreeRect);
            }
            if (_tcscmp(_T("Button"), szClassName) == 0)
            {
                ButtonRect.right = OrgMainClientRect.right - ButtonLayout[iIndex].iMargin_Right;
                ButtonRect.bottom = OrgMainClientRect.bottom - ButtonLayout[iIndex].iMargin_Bottom;
                ButtonRect.left = ButtonRect.right - ButtonLayout[iIndex].iWidth;
                ButtonRect.top =  ButtonRect.bottom - ButtonLayout[iIndex].iHeight;
                pChildWnd->MoveWindow(ButtonRect);

                iIndex++;
            }
            pChildWnd = pChildWnd->GetNextWindow();
        }

        pMainWnd->SendMessage(BFFM_SETSELECTION, TRUE, lpData);// - 设置默认选中的文件夹
        break;
    default:
        break;
    }
    return 0;
}

代码说明:
    每一句代码的注释基本很清楚。这里说一下整个的流程思路原理。回调函数只对BFFM_INITIALIZED消息进行响应,也就是只在初始化文件夹选择对话框时对对话框进行定制即可,其他消息就不用管了。
    使用TreeLayout存储文件夹树大小和布局(相对位置),使用ButtonLayout数组存储两个按钮的大小和布局。使用传进来的文件夹选择对话框句柄得到主窗口指针,然后获取子窗口和客户区。文件夹选择对话框的结构是这样的:整个窗口作为父窗口,窗口中的每一个控件作为子窗口。要对这些子窗口进行修改,则要对子窗口进行遍历,调用GetWindow(GW_CHILD)获取第一个子窗口,然后调用GetNextWindow()获取后面的子窗口,直到GetNextWindow()获取的子窗口指针为NULL为止。根据对应的控件类,存储对应的布局。再遍历一次,对控件进行调整,当然,如果要图省事,也可以一次性修改好,也是可以的。这里只是给出参考代码,可以在此基础上优化。InflateRect()函数是对矩形进行扩展,参数为正,则扩大矩形,为负数则缩小矩形。具体的请查看MSDN的解释。最后发送一个消息设置默认选中的文件夹。
    以上就是整个实现过程以及详细讲解,如果还有问题,请留言。