1.原因
前段时间需要使用OpenFileDialog选择文件夹,google上有c#的办法,比较简单,只要设置Filter=乱七八糟的符号,让所有文件都显示不出来就可以。但是这样总是有点不舒服。让我想起过去在MFC模式下创建的VC的OpenFileDialog定制,需要使用到资源文件(因为系统函数中要求你提供你的模板ID). c#也可以实现,但是必须自带res文件,这点非常麻烦,可以看这里:http://blog.csdn.net/norsd/article/details/8840761, 所以考虑生成一个c++/cli/vc.net 作为语言的.net类库
2.原理
原理非常简单,还是和过去一样: ::GetOpenFileName(&stOFN)
就是这个函数,啰嗦一下GetOpenFileName其实是一个宏,分别根据环境被定义为GetOpenFileNameA和GetOpenFileNameW
然后stOFN 是一个结构类型为:OPENFILENAME
其中为了定制,我们必须设置:OPENFILENAME::lpTemplateName = ID_DIALOG 这里很奇怪,MSDN要求的是一个string,但是我们必须传一个数字(资源号),具体原因我过去看过,一本黑皮书叫:MFC技术内部(http://book.douban.com/subject/1000127/) 里面有写过一句。
OPENFILENAME::lpfnHook 这个其实就是这个Dialog的MessageProc,其中对于一个消息返回true代表外部定制处理,false为系统默认处理。
3.头文件:
#pragma once
#include <windows.h>
#include <Commdlg.h>
#include <Commctrl.h>
#include <vector>
#pragma comment(lib,"Comdlg32.lib")
#pragma comment(lib,"user32.lib")
#pragma comment(lib,"Comctl32.lib")
#include <vcclr.h>
#include "resource.h"
using namespace std;
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Collections::Generic;
using namespace System::IO;
using namespace System::Text;
using namespace System::Runtime::InteropServices;
namespace norlib{
namespace Controls
{
public delegate UINT_PTR OFNHOOKPROCOLDSTYLE(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam);
public ref class OpenFileDialogEx
:CommonDialog
{
public:
OpenFileDialogEx(String^ arg_InitPath)
{
InitPath = arg_InitPath;
OFNHOOKPROCOLDSTYLE^ fp = gcnew OFNHOOKPROCOLDSTYLE(this,&norlib::Controls::OpenFileDialogEx::OFNHookProcOldStyle);
_gchOFNHookProcOldStyle = GCHandle::Alloc(fp);
_inrOFNHookProcOldStyle = Marshal::GetFunctionPointerForDelegate(fp);
}
~OpenFileDialogEx()
{
_gchOFNHookProcOldStyle.Free();
}
public:
//外部传入的一个字符串
property String^ InitPath;
property String^ InitFolderPath;
property String^ Title;
property String^ FileName;
property array<String^>^ FileNames;
property bool ShowReadOnly;
property bool AcceptFiles;
property bool MultiSelect;
property String^ FolderName;//For instance:"c:\\MyDir1\\MyDir2"
public:
virtual void Reset() override
{
FolderName = nullptr;
Title = nullptr;
AcceptFiles = true;
}
virtual bool RunDialog(IntPtr hwndOwner) override
{
#pragma region 分析arg_strInitPath
InitFolderPath = InitPath;
FileName = L"";
if (IO::File::Exists(InitPath))
{
InitFolderPath = IO::Path::GetDirectoryName(InitPath);
if (AcceptFiles)
{
FileName = IO::Path::GetFileName(InitPath);
}
}
#pragma endregion
pin_ptr<const wchar_t> pinTitle = PtrToStringChars(Title);
pin_ptr<const wchar_t> pinInitFolderPath = PtrToStringChars(InitFolderPath);
pin_ptr<const wchar_t> pinFileName = PtrToStringChars(FileName);
TCHAR chsFileName[FILEMAXLEN];
::memset(chsFileName,0,sizeof(chsFileName));
::wcscpy(chsFileName,pinFileName);
OPENFILENAME stOFN = {0};
stOFN.lStructSize = sizeof(OPENFILENAME);
stOFN.hwndOwner = (HWND)hwndOwner.ToInt64();
stOFN.nMaxFile = FILEMAXLEN;
stOFN.lpstrFile = (PWSTR)&chsFileName;
stOFN.lpstrInitialDir = pinInitFolderPath;
if (!AcceptFiles)
{
String^ str = String::Format("Folders\0*.{0}-{1}\0\0", Guid::NewGuid().ToString("N"), Guid::NewGuid().ToString("N"));
pin_ptr<const wchar_t> pcwStr = PtrToStringChars(str);
stOFN.lpstrFilter = pcwStr;
}
else
{
stOFN.lpstrFilter = NULL;
}
stOFN.nMaxCustFilter = 0;
stOFN.nFilterIndex = 0;
stOFN.nMaxFile = FILEMAXLEN;
stOFN.nMaxFileTitle = 0;
stOFN.lpstrTitle = pinTitle ;
stOFN.lpfnHook = (LPOFNHOOKPROC)_inrOFNHookProcOldStyle.ToPointer();
stOFN.lpTemplateName = (PCWSTR)IDD_CustomOpenDialog;
stOFN.hInstance = (HINSTANCE)(Marshal::GetHINSTANCE( this->GetType()->Module).ToInt64());
stOFN.Flags =
OFN_DONTADDTORECENT |
OFN_ENABLEHOOK |
OFN_ENABLESIZING |
OFN_NOTESTFILECREATE |
OFN_EXPLORER |
OFN_FILEMUSTEXIST |
OFN_PATHMUSTEXIST |
OFN_NODEREFERENCELINKS |
OFN_ENABLETEMPLATE |
(MultiSelect?OFN_ALLOWMULTISELECT:0)|
(ShowReadOnly?0:OFN_HIDEREADONLY);
::GetOpenFileName(&stOFN);
int extErrpr = ::CommDlgExtendedError();
if (extErrpr != 0)
{
String^ strErr = String::Format(L"创建OpenFileName对话框失败\r\n错误:{0}",extErrpr);
System::Windows::Forms::MessageBox::Show(strErr);
}
FileName = nullptr;
FileNames = nullptr;
if( _bResult )
{
PWSTR pw1st = chsFileName;
FileName = gcnew String(pw1st);
///MultiSelect返回值是
///1.文件夹路径 d:/test/dir/
///2.文件名1 Test1.txt
///3.文件名2 Test2.txt
if( MultiSelect )
{
vector<PWSTR> vtStr;
PWSTR pwFileName = pw1st;
int nIndex = wcslen(pwFileName)+1;
int nMaxIndex = FILEMAXLEN;
while( nIndex<nMaxIndex )
{
pwFileName = chsFileName+nIndex;
if(pwFileName[0]==NULL)
break;
vtStr.push_back(pwFileName);
nIndex += wcslen(pwFileName)+1;
}
int nCount = vtStr.size();
String^ strFolder = gcnew String(pw1st) + "\\";
FileNames = gcnew array<String^>(nCount);
vector<PWSTR>::iterator p;
// 指向容器的首个元素
p = vtStr.begin();
nIndex = 0;
for( ; p!= vtStr.end(); p++ )
{
FileNames[nIndex++]= strFolder + (gcnew String(*p));
}
FileName = FileNames->Length>0?FileNames[0]:FileName;
}
}
return _bResult;
}
protected:
virtual IntPtr HookProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lparam) override
{
throw "Impo!";
}
UINT_PTR OFNHookProcOldStyle(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam);
private:
void InitDialog(HWND hWnd);
void _ResizeCustomeControl();//设置自定义按钮位置
void _OnClickSelect( HWND arg_hWnd, UINT arg_uMessage, WPARAM arg_WParam, LPARAM arg_LParam );
int ProcessNotifyMessage(HWND hWnd, OFNOTIFY& notifyData);
///工具函数
private:
String^ _GetDlgItemText_T(UINT arg_uControlId)
{
HWND hItem = _GetDlgItem_T(arg_uControlId);
return _GetWindowText_T(hItem);
}
String^ _GetWindowText_T(HWND hWnd)
{
TCHAR chsText[2048]={0};
::GetWindowText(hWnd,chsText,sizeof(chsText)/sizeof(TCHAR));
return gcnew String(chsText);
}
void _EnableControl_T( UINT arg_uControlId, BOOL arg_bEnable)
{
HWND hFilterCombo = _GetDlgItem_T( arg_uControlId);
::EnableWindow( hFilterCombo, arg_bEnable );
}
void _HideControl_T( UINT arg_uControlId )
{
SendMessage(_hDlg, CDM_HIDECONTROL, arg_uControlId, 0);
}
void _SetControlText_T( UINT arg_uControlId, LPCWSTR arg_pcwText )
{
HWND hControl = _GetDlgItem_T(arg_uControlId);
::SetWindowText(hControl,arg_pcwText);
}
WINDOWPLACEMENT _GetPlacement_T( UINT arg_uControlId )
{
WINDOWPLACEMENT stPlacement;
HWND hControl = _GetDlgItem_T(arg_uControlId);
::GetWindowPlacement(hControl,&stPlacement);
return stPlacement;
}
void _SetPlacement_T( UINT arg_uControlId, WINDOWPLACEMENT& arg_stPlacement )
{
HWND hControl = _GetDlgItem_T(arg_uControlId);
SetWindowPlacement(hControl,&arg_stPlacement);
}
HWND _GetDlgItem_T(UINT arg_uControlId)
{
HWND hwnd = GetDlgItem(_hDlg,arg_uControlId);
if( hwnd == NULL )
{
return GetDlgItem(_hThis,arg_uControlId);
}
return hwnd;
}
UINT_PTR _GetFont_T( UINT arg_uControlId)
{
HWND hWnd = _GetDlgItem_T( arg_uControlId );
return SendMessage(hWnd,WM_GETFONT,0,0);
}
void _SetFont_T(UINT arg_uControlId, UINT_PTR arg_hFont)
{
HWND hWnd = _GetDlgItem_T( arg_uControlId );
SendMessage(hWnd, WM_SETFONT, arg_hFont, 0);
}
String^ _GetFolderPath_T()
{
TCHAR chsText[2048];
CommDlg_OpenSave_GetFolderPath(_hDlg,chsText,sizeof(chsText)/sizeof(TCHAR));
return gcnew String(chsText);
}
private:
///有两层结构见InitDialog
HWND _hThis;
HWND _hDlg;
GCHandle _gchOFNHookProcOldStyle;
IntPtr _inrOFNHookProcOldStyle;
bool _bResult;
static const int FILEMAXLEN=2048;
};
}
}
实现文件:
// This is the main DLL file.
#include "stdafx.h"
#include "Controls.OpenFileDialogEx.h"
void norlib::Controls::OpenFileDialogEx::_OnClickSelect( HWND arg_hWnd, UINT arg_uMessage, WPARAM arg_WParam, LPARAM arg_LParam )
{
if( AcceptFiles )
{
SendMessage(arg_hWnd, arg_uMessage, arg_WParam, arg_LParam);
}
else
{
//处理Folder
String^ strFolderPath = _GetFolderPath_T();
//绝对路径
if (IO::Path::IsPathRooted(strFolderPath))
{
if (Directory::Exists(strFolderPath))
{
FolderName = strFolderPath;
_bResult = true;
::SendMessage( _hDlg, WM_CLOSE, 0, 0);
}
}
相对路径
//else if (!String::IsNullOrEmpty(m_currentFolder) && strFileNameCombo != "")
//{
// var combined = System::IO::Path::Combine(m_currentFolder, currentText);
// if (Directory.Exists(combined))
// {
// //the contents of the text box are a relative path, that points to a
// //an existing directory. We interpret the users intent to mean that they wanted
// //to select the existing path.
// m_useCurrentDir = true;
// m_currentFolder = combined;
// hParent.SendMessage(InteropUtil.WM_CLOSE, 0, 0);
// break;
// }
//}
The user has not selected an existing folder.
So we translate a click of our "Select" button into the OK button and forward the request to the
open file dialog.
//hParent.SendMessage
// (
// InteropUtil.WM_COMMAND,
// (InteropUtil.BN_CLICKED << 16) | InteropUtil.IDOK,
// unchecked((uint)hParent.GetDlgItem(InteropUtil.IDOK))
// );
}
}
int norlib::Controls::OpenFileDialogEx::ProcessNotifyMessage( HWND hWnd, OFNOTIFY& notifyData )
{
switch (notifyData.hdr.code)
{
case CDN_FOLDERCHANGE:
{
//String^ newFolder = GetTextFromCommonDialog( ::GetParent(hWnd), CDM_GETFOLDERPATH);
//if (m_currentFolder != nullptr && newFolder != nullptr && newFolder->PathContains(m_currentFolder))
//{
// m_suppressSelectionChange = true;
//}
//m_currentFolder = newFolder;
//var fileNameCombo = hWnd.GetParent().AssumeNonZero().GetDlgItem(InteropUtil.ID_FileNameCombo).AssumeNonZero();
//if (m_hasDirChangeFired)
//{
// fileNameCombo.SetWindowTextW("");
//}
//m_hasDirChangeFired = true;
break;
}
case CDN_FILEOK:
{
if (!AcceptFiles)
{
return 1;
}
break;
}
case CDN_INITDONE:
{
HWND hParent = ::GetParent(hWnd);
HWND hFile = ::GetDlgItem(hParent, ID_FileNameTextCombo);
::SetFocus(hFile);
break;
}
}
return 0;
}
void norlib::Controls::OpenFileDialogEx::_ResizeCustomeControl()
{
WINDOWPLACEMENT locCancel = _GetPlacement_T(IDCANCEL);
WINDOWPLACEMENT locSelect = _GetPlacement_T(ID_SELECT);
locSelect.rcNormalPosition.right = _GetPlacement_T(ID_FileNameTextCombo).rcNormalPosition.right;
_SetPlacement_T(ID_CUSTOM_CANCEL,locCancel );
RECT& rcCancel = locCancel.rcNormalPosition;
RECT& rc = locSelect.rcNormalPosition;
rc = rcCancel;
rc.right = rc.left-10;
rc.left = rc.right-(rcCancel.right-rcCancel.left);
_SetPlacement_T(ID_SELECT,locSelect);
HWND hSelectBtn = _GetDlgItem_T(ID_SELECT);
HWND hCacelBtn = _GetDlgItem_T(ID_CUSTOM_CANCEL);
InvalidateRect(hSelectBtn,NULL,TRUE);
InvalidateRect(hCacelBtn,NULL,TRUE);
}
void norlib::Controls::OpenFileDialogEx::InitDialog( HWND hWnd )
{
_hDlg = ::GetParent(hWnd);
_hThis = hWnd;
_EnableControl_T(ID_FilterCombo,FALSE);
_HideControl_T(ID_FilterCombo);
_HideControl_T(ID_FilterLabel);
//We don't want the accelerator keys for the ok and cancel buttons to work, because
//they are not shown on the dialog. However, we still want the buttons enabled
//so that "esc" and "enter" have the behavior they used to. So, we just
//clear out their text instead.
_SetControlText_T(IDOK,L"");
_SetControlText_T(IDCANCEL,L"");
//find our button controls
_SetFont_T( ID_SELECT, _GetFont_T(IDOK) );
_SetFont_T( ID_CUSTOM_CANCEL, _GetFont_T(IDCANCEL));
WINDOWPLACEMENT cancelLoc = _GetPlacement_T(IDCANCEL);
//hide the ok and cancel buttons
_HideControl_T(IDCANCEL);
_HideControl_T(IDOK);
//expand the file name combo to take up the space left by the OK and cancel buttons.
WINDOWPLACEMENT fileNameLoc = _GetPlacement_T(ID_FileNameTextCombo);
WINDOWPLACEMENT okbuttonLoc = _GetPlacement_T(IDOK);
fileNameLoc.rcNormalPosition.right = okbuttonLoc.rcNormalPosition.right;
_SetPlacement_T(ID_FileNameTextCombo,fileNameLoc);
if(!AcceptFiles)
{
_SetControlText_T(ID_FileNameLabel,L"Folder Name:");
}
WINDOWPLACEMENT parentLoc;
GetWindowPlacement(_hDlg,&parentLoc);
//subtract the height of the missing cancel button
parentLoc.rcNormalPosition.bottom -= (cancelLoc.rcNormalPosition.bottom - cancelLoc.rcNormalPosition.top);
SetWindowPlacement(_hDlg , &parentLoc);
//move the select and custom cancel buttons to the right hand side of the window:
WINDOWPLACEMENT selectLoc = _GetPlacement_T(ID_SELECT);
WINDOWPLACEMENT customCancelLoc = _GetPlacement_T(ID_CUSTOM_CANCEL);
WINDOWPLACEMENT ctrlLoc;
GetWindowPlacement(hWnd,&ctrlLoc);
ctrlLoc.rcNormalPosition.right = fileNameLoc.rcNormalPosition.right;
}
UINT_PTR norlib::Controls::OpenFileDialogEx::OFNHookProcOldStyle( HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam )
{
switch(uMsg)
{
case WM_INITDIALOG:
{
InitDialog(hDlg);
break;
}
case WM_NOTIFY:
{
OFNOTIFY* pNotifyData = (OFNOTIFY*)lParam;
UINT_PTR results = ProcessNotifyMessage(hDlg, *pNotifyData);
if (results != 0)
{
//http://msdn.microsoft.com/ZH-CN/library/windows/desktop/ms633591(v=vs.85).aspx
//::SetWindowLong(hDlg, DWL_MSGRESULT, results);
//64bit http://sourceforge.net/p/bochs/bugs/1250/
::SetWindowLongPtr(hDlg,DWLP_MSGRESULT,results);
//If you use SetWindowLongPtr with the DWLP_MSGRESULT index to set the return value for a message processed by a dialog box procedure,
//the dialog box procedure should return TRUE directly afterward.
//Otherwise, if you call any function that results in your dialog box procedure receiving a window message,
//the nested window message could overwrite the return value you set by using DWLP_MSGRESULT.
return TRUE;
}
break;
}
case WM_SIZE:
{
_ResizeCustomeControl();
break;
}
case WM_COMMAND:
{
HWND hParent = GetParent(hDlg);
WORD code = HIWORD(wParam);
WORD id = LOWORD(wParam);
if (code == BN_CLICKED)
{
switch (id)
{
case ID_CUSTOM_CANCEL:
{
//The user clicked our custom cancel button. Close the dialog.
SendMessage(hParent, WM_CLOSE, 0, 0);
break;
}
case ID_SELECT:
{
_OnClickSelect(hParent,WM_COMMAND,IDOK,NULL);
break;
}
}
}
break;
}
}
return 0;
}
app.rc文件
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"
/
#undef APSTUDIO_READONLY_SYMBOLS
/
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
/
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
1 ICON "app.ico"
#ifdef APSTUDIO_INVOKED
/
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
"\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""afxres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\0"
END
#endif // APSTUDIO_INVOKED
/
//
// Dialog
//
IDD_CustomOpenDialog DIALOGEX 0, 0, 177, 17
STYLE DS_SETFONT | DS_3DLOOK | DS_CONTROL | WS_CHILD | WS_CAPTION | WS_TABSTOP
FONT 8, "MS Sans Serif", 0, 0, 0x0
BEGIN
DEFPUSHBUTTON "&Select",ID_SELECT,3,0,50,15
PUSHBUTTON "&Cancel",ID_CUSTOM_CANCEL,59,0,50,15
END
/
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_CustomOpenDialog, DIALOG
BEGIN
RIGHTMARGIN, 174
END
END
#endif // APSTUDIO_INVOKED
#endif // English (United States) resources
/
resource.h文件
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by app.rc
//
#define IDD_CustomOpenDialog 101
#define IDI_ICON1 105
#define ID_SELECT 1001
#define ID_CUSTOM_CANCEL 1002
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
#define IDOK 1
#define IDCANCEL 2
//control aliases that actually make sense....
#define ID_FilterCombo 0x0470
#define ID_FilterLabel 0x0441
#define ID_FileNameLabel 0x0442
#define ID_FileNameTextBox 0x0480
#define ID_FileNameTextCombo 0x047c
#define ID_FileList 0x0461
里面的一些技术细节非常简单,无非就是隐藏原有的2个ok,cancel按钮,然后替换我们自己的按钮,不懂的可以问。
源代码就这些了。
编译完成后就可以用在.net上了