65.9K
CodeProject 正在变化。 阅读更多。
Home

属性表 Shell 扩展 AppWizard

starIconstarIconemptyStarIconemptyStarIconemptyStarIcon

2.00/5 (1投票)

2001 年 1 月 17 日

viewsIcon

86600

downloadIcon

1498

一个帮助开始使用属性表 Shell 扩展的向导

本文旨在简化属性表外壳扩展的实现。它是我第一篇关于上下文菜单外壳扩展的文章,并受到Michael Dunn非常出色的外壳扩展编写教程系列的启发。我强烈建议您通读它,以便对后续内容有充分的理解。

这次,我们将解决实现属性表外壳扩展的方案。我们将重用我上一篇文章中的材料,并添加几行代码。所有这些都打包在一个简洁的属性表外壳扩展AppWizard中。

安装和运行向导

属性表外壳扩展向导包含一个自包含的propsheetapp.awx文件,需要将其复制到您的模板文件夹中。模板文件夹位于您的主Microsoft Visual Studio安装文件夹下,在\Common\MSDev98\Template文件夹中。

通过使用触发新建项目对话框的“文件|新建”菜单项来激活向导。

 [VC New dialog - 3K]

选择属性表外壳扩展项会显示以下向导对话框。此步骤允许指定外壳扩展将注册到的文件类型,以及向导将生成的C++类名。

 [AppWizard dialog - 10K]

向导提供了什么?

向导会生成一个与ATL/COM AppWizard创建的项目类似的Visual Studio项目。主要区别在于项目已经包含了一个实现属性表外壳扩展骨架的类。

回想Michael Dunn的教程,属性表外壳扩展是一个COM对象,它应该实现IShellExtInitIShellPropSheetExt接口。向导生成的类已经提供了所有必需的,以支持这些实现,形式为一个名为atlshellex.h的头文件,它与我上一篇文章中的相同,但已更新,并添加了几行代码来充实IShellPropSheetExt接口的实现。

向导生成的代码利用了WTL对ATL的扩展。这些扩展非常方便,并且没有明显的理由不使用它们。这些扩展简化了属性页本身和对话框控件的编程。向导已经生成了一个初始属性页类。它已经知道如何响应WM_INITDIALOGPSM_APPLY消息。

让我们从一个示例开始

让我们尝试编写一个类似于教程中描述的外壳扩展。这次没那么容易,因为属性表有点复杂,而且大部分工作都需要您自己完成。但是,创建和显示属性页的所有繁重工作都已经由向导生成了。

从上面的屏幕截图可以看出,我们选择将外壳扩展钩接到.TXT文件,就像Michael的一样。还请注意,在本例中实现扩展的类名为CShellExt

如果您查看CShellExtAddPages()方法的实现,您会看到以下几行

STDMETHODIMP CShellExt::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
{
	CSamplePropPage* m_pPage;

	m_pPage = new CSamplePropPage;
	m_pPage->SetTitle(_T("Sample"));
	m_pPage->SetFile(m_files[0]);

	HPROPSHEETPAGE hPage = m_pPage->Create();
	if (!lpfnAddPage(hPage, lParam)) delete m_pPage;
	return S_OK;
}

这段由向导生成的代码实际上创建了一个属性页实例,并为其分配了一个基于项目名称的占位符标题。它还将第一个选定文件的名称传递给属性表,并调用外壳的回调函数来执行页面添加到属性表的操作。请注意,属性页类名是根据您的项目名称合成的,在本例中,项目是Sample,因此是CSamplePropPage类。

如果我们像Michael在他的例子中那样需要添加更多页面,那么这就是放置它们的地方。我在这里的例子中不会这样做。只需说明您只需要遍历m_files数组并为每个文件创建其他属性页实例。

令人惊讶的是,WTL对属性页的实现并没有提供设置属性页选项卡中小图标的方法。所以我们需要自己添加。此外,我们希望在选项卡中显示文件名。下面概述了所需的更改

#include "shlwapi.h"
...
STDMETHODIMP CShellExt::AddPages(LPFNADDPROPSHEETPAGE lpfnAddPage, LPARAM lParam)
{
	CSamplePropPage* m_pPage;
	m_pPage = new CSamplePropPage;

	// Add a small icon (assuming a resource identifier IDI_ICON)
COLOR="red">
	m_pPage->m_psp.pszIcon = MAKEINTRESOURCE(IDI_ICON);
	m_pPage->m_psp.dwFlags |= PSP_USEICONID;

	// Use the file name as the title
	TCHAR szFile[_MAX_FNAME];
	lstrcpy(szFile, m_files[0]);
	::PathStripPath(szFile);	

	m_pPage->SetTitle(szFile);
	m_pPage->SetFile(m_files[0]);

	HPROPSHEETPAGE hPage = m_pPage->Create();
	if (!lpfnAddPage(hPage, lParam)) delete m_pPage;
	return S_OK;
}

请确保将shlwapi.lib添加到项目设置的库列表中。

现在我们已经有了基本的属性表并正常运行,我们需要为CSamplePropPage类添加更有趣的功能。我们将在对话框模板中添加五个日期和时间选择器控件,以显示各种日期和时间,以及一个额外的静态控件来显示完整的文件路径。

 [Property page dlg - 3K]

为了简化我们的工作,我们将使用SetCombinedDatetime()GetCombinedDateTime()辅助函数,它们分别从一对日期和时间选择器控件设置和获取日期和时间部分。这些函数代码改编自Michael的项目

void GetCombinedDateTime ( HWND hwnd, UINT idcDatePicker, UINT idcTimePicker,
                           FILETIME* pFiletime )
{
	SYSTEMTIME st = {0}, stDate = {0}, stTime = {0};
	FILETIME   ftLocal;

	CDateTimePickerCtrl dtControl;
	dtControl.Attach(::GetDlgItem(hwnd, idcDatePicker));
	dtControl.GetSystemTime(&stDate);

	if (idcTimePicker != 0) {
		dtControl.Attach(::GetDlgItem(hwnd, idcTimePicker));
		dtControl.GetSystemTime(&stTime);
	}

	st.wMonth  = stDate.wMonth;
	st.wDay    = stDate.wDay;
	st.wYear   = stDate.wYear;
	st.wHour   = stTime.wHour;
	st.wMinute = stTime.wMinute;
	st.wSecond = stTime.wSecond;

	::SystemTimeToFileTime (&st, &ftLocal);
	::LocalFileTimeToFileTime (&ftLocal, pFiletime);

	dtControl.Detach();
}
void SetCombinedDateTime ( HWND hwnd, UINT idcDatePicker, UINT idcTimePicker,
                           const FILETIME* pFiletime )
{
	SYSTEMTIME st;
	FILETIME   ftLocal;

	::FileTimeToLocalFileTime (pFiletime, &ftLocal);
	::FileTimeToSystemTime (&ftLocal, &st);

	CDateTimePickerCtrl dtControl;
	dtControl.Attach(::GetDlgItem(hwnd, idcDatePicker));
	dtControl.SetSystemTime(GDT_VALID, &st);

	if (idcTimePicker != 0) {
		dtControl.Attach(::GetDlgItem(hwnd, idcTimePicker));
		dtControl.SetSystemTime(GDT_VALID, &st);
	}

	dtControl.Detach();
}

更新这些控件的代码是标准的WTL控件编程,发生在OnInitDialog()函数中。这个函数是由向导生成的,所以这里是您需要添加的几行

LRESULT CSamplePropPage::OnInitDialog(HWND /*hWnd*/, LPARAM /*lParam*/)
{
	// Display the full path
	CStatic sPath;
	sPath.Attach(::GetDlgItem(m_hWnd, IDC_FILE));
	sPath.SetWindowText(m_file);
	sPath.Detach();

	// Get the file dates and times

	FILETIME creationTime;
	FILETIME accessTime;
	FILETIME modificationTime;

	HANDLE hFile;

	hFile = ::CreateFile(m_file, GENERIC_READ,
					FILE_SHARE_READ, 0,
					OPEN_EXISTING, 0, 0);
	ATLASSERT(hFile != INVALID_HANDLE_VALUE);

	::GetFileTime(hFile, &creationTime, &accessTime, &modificationTime);
	::CloseHandle(hFile);

	// Display the creation/modification/access date and time

	SetCombinedDateTime(m_hWnd, IDC_CREATIONDATE, IDC_CREATIONTIME, &creationTime);
	SetCombinedDateTime(m_hWnd, IDC_MODIFICATIONDATE, IDC_MODIFICATIONTIME, &modificationTime);
	SetCombinedDateTime(m_hWnd, IDC_ACCESSDATE, 0, &accessTime);

	return 0L;
}

为了启用“应用”按钮,我们需要捕获从日期和时间选择器控件发送的通知。它们会向属性表发送DTN_DATETIMECHANGE通知。我们只需要在消息映射中添加一个宏,并在类中添加相应的消息处理程序来完成这项工作。请注意,由于我们使用的是WTL,我使用了消息映射宏的_EX版本,它们执行消息解析,但如果您愿意,也可以随时使用ATL提供的宏。

class CSamplePropPage : public CPropertyPageImpl<CSamplePropPage>
{
...
	LRESULT OnDateTimeChanged(LPNMHDR lpnmhdr);
...
// Message map
public:
BEGIN_MSG_MAP_EX(CSamplePropPage)
	CHAIN_MSG_MAP(CPropertyPageImpll<CSamplePropPage>)
	MSG_WM_INITDIALOG(OnInitDialog)
	NOTIFY_RANGE_CODE_HANDLER_EX(IDC_CREATIONDATE, IDC_ACCESSDATE, DTN_DATETIMECHANGE, OnDateTimeChanged)
END_MSG_MAP()
};

我假设日期和时间选择器控件的资源标识符是连续的,这样我就可以使用范围处理程序将来自所有这些控件的通知路由到一个单一的消息处理程序。OnDateTimeChanged()函数的实现很简单。只需启用“应用”按钮...

LRESULT CSamplePropPage::OnDateTimeChanged(LPNMHDR /*lpnmhdr*/)
{
	SetModified(TRUE);
	return 0L;
}

点击“应用”按钮现在将触发由向导生成的OnApply()函数。这是一个重写的函数,每当用户点击“确定”或“应用”按钮时都会被调用。我们需要将所做的日期和时间修改应用回文件。

BOOL CSamplePropPage::OnApply(void)
{
	FILETIME creationTime;
	FILETIME accessTime;
	FILETIME modificationTime;

	// Get the new creation/modification/access date and time

	GetCombinedDateTime(m_hWnd, IDC_CREATIONDATE, IDC_CREATIONTIME, &creationTime);
	GetCombinedDateTime(m_hWnd, IDC_MODIFICATIONDATE, IDC_MODIFICATIONTIME, &modificationTime);
	GetCombinedDateTime(m_hWnd, IDC_ACCESSDATE, 0, &accessTime);

	// Set the file dates and times
	HANDLE hFile;

	hFile = ::CreateFile(m_file, GENERIC_WRITE,
					0, 0, OPEN_EXISTING,
					FILE_ATTRIBUTE_NORMAL, 0);
	ATLASSERT(hFile != INVALID_HANDLE_VALUE);

	::SetFileTime(hFile, &creationTime, &accessTime, &modificationTime);
	::CloseHandle(hFile);

	return TRUE;
}

就是这样!项目现在已完成。

结论

正如您所见,实现上下文菜单扩展现在更快更容易。此外,您可以在自己的项目中重用提供的atlshellex.h文件。这个文件atlshellex.h以一种与实现我前一篇文章中概述的上下文菜单外壳扩展直接相关的方式实现了IShellExtInit接口。

我希望本文足够清晰。如果您有任何问题,请告诉我。

© . All rights reserved.