现在的位置: 首页 > 综合 > 正文

C++/CLI/VC.NET 实现OpenFileDialog的定制,使他可以选择文件夹

2012年10月24日 ⁄ 综合 ⁄ 共 14534字 ⁄ 字号 评论关闭
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上了

抱歉!评论已关闭.