Windows 7:在一个应用程序中探索 7 项令人兴奋的新编程功能!






4.93/5 (72投票s)
七项新编程技术的演示。
引言
欢迎来到 Windows 7 的世界。我将在这里尝试演示七个最吸引人的编程功能集合,您可以使用它们来增强应用程序的外观和内部!
本文是 CodeProject Windows 7 竞赛的候选文章。如果您喜欢本文,请为我投票!
神奇的数字七
本文讨论以下主题
所有这些都用于示例应用程序“Windows 7 下载器”,该应用程序用于下载文件。它使用任务栏扩展显示进度条和工具栏,将文件存储到库位置,使用 Direct2D 绘制进度和信息,允许触摸屏幕以选择传输,使用动画管理器动画传输进度,根据光照条件更改绘图(使用 SDK 中的虚拟光传感器工具),如果安装了 GPS 传感器,则计算并显示您的位置,使用 Google 地图绘制从您的位置到目标文件位置的虚拟路径。最后,所有这些都通过 Ribbon 界面呈现。
本文中粘贴的代码为了简洁起见,已删除了错误检查函数。您应该始终检查函数的 HRESULT
值!此外,大多数已知代码(如 IUnknown
实现)也已从中删除。要获取完整代码,请参阅 CPP 文件。
包含
- 一体化源文件
- TBAR.CPP - 任务栏扩展
- LIBR.CPP - 库
- TOUCH.CPP - 触摸示例
- SENS.CPP - 传感器资源管理器
- ANIM.CPP - 动画管理器
- D2D.CPP - Direct2D 示例
- RIB.CPP - Ribbon
- MAIN.CPP/RC/Hs - 主要内容
- Ribbon 位图
- Visual Studio 2008 解决方案和项目文件 (VCPROJ 和 SLN)
- Visual Ribbon Creator 项目文件 (VRC)
- x86/x64 可执行文件
所需软件
- Windows 7。如果您仍然使用 RC1,大部分代码都可以使用,但是,RC1 SDK 中的一些定义已更改。
- Windows 7 SDK.
- Visual Studio 2008 或 Visual Studio 2010。
可选/有用的软件
- Visual Ribbon Creator - 如果您厌倦了手动编写 Ribbon 代码,这是我的设计器!
- GPS Sensor Driver - 我的驱动程序,用于测试传感器和位置 API,提供来自任何可以通过串口(USB 或蓝牙)连接的 NMEA 兼容 GPS 的真实数据。如果您没有实际的 GPS 硬件,此驱动程序也可以模拟您的位置。
- CodePlex 触摸模拟。如果您还没有触摸屏,此工具包含一个驱动程序,可以使用鼠标模拟触摸手势。
- Windows 7 设备驱动程序工具包,用于编写您自己的传感器驱动程序。
延伸阅读
通用 API
除了少数例外,本文讨论的所有 API 都是 COM 功能。也就是说,即使不在 Windows 7 下运行,您的应用程序也将继续工作 - 因此几乎没有理由不实现它们!
有一些函数(例如,用于创建 Direct2D 接口的函数)您应该调用 LoadLibrary
/GetProcAddress
,否则,您的应用程序在不在 Windows 7 下运行时将无法初始化。
某些功能(特别是 Ribbon、Direct2D 和动画管理器)也可以通过适用于 Vista SP2 的平台更新在 Vista 中使用。
一、任务栏扩展
它们是什么?
应用程序使用这些扩展来
- 指定任务栏右键单击(跳转列表)的最近/常用项目。
- 显示工具栏以操作应用程序而无需切换到应用程序。
- 将任务栏用作进度条。
|
|
|
图 1.1:跳转列表
|
图 1.2:工具栏
|
图 1.3:进度条
|
重要提示 #1:所有任务栏操作都必须在您的应用程序知道任务栏按钮已创建后发生。调用 RegisterWindowMessage
(L"TaskbarButtonCreated") 以获取消息 ID,当您在循环中收到此消息时,然后执行任务栏操作。不这样做将花费您数小时的调试时间,就像发生在我身上一样 :)
重要提示 #2:如果您以管理员身份运行应用程序,则无法使用操作工具栏,因为 Explorer 在中等完整性下运行,因此无法向您的应用程序发送消息。如果您确实希望 Explorer 在高完整性模式下能够与您的应用程序通信,则必须对 WM_COMMAND
和 RegisterWindowMessage(L"TaskbarButtonCreated")
返回的消息使用 ChangeWindowMessageFilterEx
。
快速步骤
- 指定最近项目
- 通过调用
SetCurrentProcessExplicitAppUserModelID()
设置唯一的应用程序 ID。 CoCreate
ITaskbarList3
。- 使用
SHAddToRecentDocs()
操作最近文档。 CoCreate
ICustomDestinationList
。- 显示操作工具栏
CoCreate
ITaskbarList3
。- 使用成员函数
ThumbBarSetImageList()
将图像列表添加到任务栏。 - 使用成员函数
ThumbBarAddButtons()
添加按钮。 - 单击按钮时,您的应用程序将收到
WM_COMMAND
消息。 - 将任务栏用作进度条
CoCreate
ITaskbarList3
。- 调用成员
SetProgressState()
设置状态。 - 要更新状态,请调用成员
SetProgressValue()
。
逐步操作最近项目(为简洁起见,已删除错误处理)
有两个已知类别您不需要定义任何内容,即最近项目和常用项目。这些项目必须是这些类别的文件名。如果您想添加任何其他类型的类别,您必须实现自己的 IObjectArray
。有关更多信息,请参阅 ICustomDestinationList::AppendCategory
文档。
// Globals
const wchar_t* OurAppID = L"You.Software.Sub.Version";
unsigned long uumsg = 0;
vector<wstring> SomeRecentDocuments;
// In WinMain
// Set the AppID
SetCurrentProcessExplicitAppUserModelID(OurAppID);
// Get message
uumsg = RegisterWindowMessage(L"TaskbarButtonCreated");
// In Message Loop
if (msg == uumsg)
{
ITaskbarList3* tb = 0;
CoCreateInstance(CLSID_TaskbarList,0,CLSCTX_INPROC_SERVER,
__uuidof(ITaskbarList3),(void**)&tb);
// Put the MRU
SHAddToRecentDocs(SHARD_PATHW,0);
for(unsigned int i = 0 ; i < SomeRecentDocuments.size() ; i++)
{
SHAddToRecentDocs(SHARD_PATHW,(void*)SomeRecentDocuments[i].c_str());
}
// Use the Destination List
ICustomDestinationList* cdl = 0;
CoCreateInstance(CLSID_DestinationList,NULL,CLSCTX_INPROC_SERVER,
__uuidof(ICustomDestinationList),(void**)&cdl);
// Set AppID
cdl->SetAppID(OurAppID);
// Recent Items begin list
UINT MaxCount = 0;
IObjectArray* oa = 0;
hr = cdl->BeginList(&MaxCount,__uuidof(IObjectArray),(void**)&oa);
// Add known category
hr = cdl->AppendKnownCategory(KDC_RECENT);
oa->Release();
odl->Release();
tb->Release();
}
逐步操作任务栏工具栏(为简洁起见,已删除错误处理)
// Create the interface
ITaskbarList3* tb = 0;
CoCreateInstance(CLSID_TaskbarList,0,CLSCTX_INPROC_SERVER,
__uuidof(ITaskbarList3),(void**)&tb);
// Load the toolbar
HBITMAP hB = SomeBitmap();
LoadTransparentToolbarImage(hAppInstance,_T("WM7_TOOLBAR"),0xFFFFFFFF);
unsigned int nI = 2; // 2 Images.
HIMAGELIST tlbi = ImageList_Create(bi.bmHeight,bi.bmHeight,ILC_COLOR32,nI,0);
// Add the bitmap
ImageList_Add(tlbi,hB,0);
tb->ThumbBarSetImageList(MainWindow,tlbi);
DeleteObject(hB);
// Add 2 buttons:
THUMBBUTTON tup[2];
int ids[] = {701,702};
wchar_t* txts[] = {L"Stop all",L"Start all"};
for(int i = 0 ; i < 2 ; i++)
{
THUMBBUTTON& tu = tup[i];
tu.dwMask = THB_FLAGS | THB_BITMAP | THB_TOOLTIP;
tu.iBitmap = i;
tu.iId = ids[i];
_tcscpy(tu.szTip,txts[i]);
tu.dwFlags = THBF_ENABLED | THBF_DISMISSONCLICK;
}
tb->ThumbBarAddButtons(MainWindow,2,tup);
tb->Release();
逐步操作进度条(为简洁起见,已删除错误处理)
// Create the interface
ITaskbarList3* tb = 0;
CoCreateInstance(CLSID_TaskbarList,0,CLSCTX_INPROC_SERVER,
__uuidof(ITaskbarList3),(void**)&tb);
// Set the state
tb->SetProgressState(MainWindow,TBPF_NORMAL);
// To display progress
tb->SetProgressValue(MainWindow,35,100); // 35% progress
tb->Release();
阅读更多
- 任务栏扩展 文档。
- ITaskbarList3 接口。
二、库
什么是库?
库 是一种告诉 Windows 7 您的文件存储在哪里f的方式。“我的文档”文件夹的问题是只有一个;用户可能希望将重要文件存储在硬盘的各个位置。库告诉 Windows 您的收藏集在哪里,以便可以轻松地对其进行索引和搜索。因此,一个库可以包含许多文件,它们被分类为单个文件夹中的文件,而实际上它们位于硬盘的不同目录中。它基本上是多个目录的容器;这降低了应用程序的复杂性 - 例如,一个需要在文件更改时收到通知的应用程序,现在只需要监视库对象,该对象会自动监视其包含的所有项目。
例如,一个人可能希望将他们从克里特岛拍摄的所有照片存储在特殊位置(取决于他们何时访问该岛),但为了能够一次性搜索所有照片,他们只需定义一个包含所有照片的库,然后对这些照片进行搜索、索引和排序,就好像它们属于单个目录一样。
由于库是 Shell 界面,因此它可以用于任何类型的应用程序,而不仅仅是我们的下载器。
在“另存为”中启用库选择
要启用库的使用,您可以通常调用 GetSaveFileName()
,但为了获得更大的灵活性,您可以使用 IFileSaveDialog
void SelectFileLocation(HWND hh,wstring& fn)
{
HRESULT hr = S_OK;
IShellItem* si = 0;
IFileSaveDialog* fs;
hr = CoCreateInstance(CLSID_FileSaveDialog,NULL,CLSCTX_INPROC,
__uuidof(IFileSaveDialog),(void**)&fs);
if (SUCCEEDED(hr))
{
FILEOPENDIALOGOPTIONS fop;
hr = fs->GetOptions(&fop);
fop |= FOS_OVERWRITEPROMPT | FOS_PATHMUSTEXIST | FOS_FORCESHOWHIDDEN;
hr = fs->SetOptions(fop);
hr = fs->SetFileName(fn.c_str());
if (SUCCEEDED(hr))
{
hr = fs->Show(hh);
if (SUCCEEDED(hr))
{
hr = fs->GetResult(&si);
}
}
fs->Release();
}
if (!si)
return;
LPWSTR ff = 0;
si->GetDisplayName(SIGDN_FILESYSPATH,&ff);
if (ff)
{
fn = ff;
CoTaskMemFree(ff);
}
si->Release();
}
上述函数返回用户选择。如果您选择一个库,则返回该库的默认保存位置。(您可以使用 IShellLibrary::GetDefaultSaveFolder 设置此项)。
在“文件打开”中启用库选择
这有所不同,因为库不是文件系统对象。您必须使用 IShellLibrary
,如 MSDN 所述
IShellLibrary *picturesLibrary;
hr = SHLoadLibraryFromKnownFolder(FOLDERID_PicturesLibrary,
STGM_READ, IID_PPV_ARGS(&picturesLibrary));
// picturesLibrary points to the user's picture library
IShellItemArray *pictureFolders;
hr = pslLibrary->GetFolders(LFF_FORCEFILESYSTEM,
IID_PPV_ARGS(&pictureFolders));
// pictureFolders contains an array of shell items that represent
// the folders found in the user's pictures library
创建和使用库
我们的应用程序创建了一个“W7DL”库。对于您使用它下载的每个文件(默认位置是 我的文档\\W7DL),其目录都存储为该库中的一个项目,以便您可以在一个目的地访问所有下载内容
首先,如果库不存在,则创建它
// Creates a W7Downloader Library if not existing wstring SaveF;
GetSaveFolder(SaveF); // Gets default save folder, my documents\W7DL
// First, make sure the library exists
IShellLibrary* sl = 0;
SHCreateLibrary(__uuidof(IShellLibrary),(void**)&sl);
if (!sl)
return;
IShellItem* si = 0;
sl->SaveInKnownFolder(FOLDERID_Libraries,
L"W7Downloads",LSF_FAILIFTHERE,&si);
if (si)
si->Release();
si = 0;
sl->Release();
sl = 0;
以上代码确保我们有一个库。LFS_FAILIFTHERE
确保现有库不会被覆盖。
现在要将库加载到 IShellFolder
中,我们使用此代码
// Load the library
TCHAR ln[10000] = {0};
PWSTR LibraryFolderPath = 0;
SHGetKnownFolderPath(FOLDERID_Libraries,0,0,&LibraryFolderPath);
if (!LibraryFolderPath)
return;
swprintf_s(ln,10000,L"%s\\W7Downloads.library-ms",LibraryFolderPath);
// library-ms seems to be the extension for library files
CoTaskMemFree(LibraryFolderPath);
hr = SHLoadLibraryFromParsingName(ln,STGM_READWRITE,
__uuidof(IShellLibrary),(void**)&sl);
if (!sl)
return;
我们现在要将我们的默认保存文件夹添加到此库。由于它是我们添加的第一个目录,它将被标记为默认。
// Add the SaveF folder to this library
hr = SHAddFolderPathToLibrary(sl,SaveF.c_str());
sl->Commit();
要添加更多目录,我们使用相同的函数。
阅读更多
- 库 文档。
- IShellLibrary 接口。
三、触摸 API
这是什么?
触摸 API 是一个新的 API,用于处理来自多点触控表面上的多个接触点(触摸)。它支持两条消息
WM_GESTURE
,如果触摸被识别为手势,则传递有关手势的信息,以及WM_TOUCH
,传递有关多次触摸的信息。
这里我们将重点关注 WM_TOUCH
。
嘿,有人有触摸屏了吗?
我不确定,但我有一个好消息要告诉您。借助 Codeplex 触摸模拟工具,您可以使用鼠标(或多只鼠标)模拟触摸。此工具将您的鼠标转换为虚拟触摸设备。此视频展示了如何安装和启用此模拟器。请注意,您必须“禁用”鼠标功能才能使应用程序实际接收 WM_TOUCH
。
注册 WM_TOUCH
窗口必须使用 RegisterTouchWindow 注册才能接收 WM_TOUCH
。如果该窗口有子窗口,则必须为每个子窗口单独调用 RegisterTouchWindow()
。
处理 WM_TOUCH
wParam
的低字包含触摸次数,lParam
包含一个句柄。您可以使用此句柄调用 GetTouchInputInfo,以获取 TOUCHINPUT
结构数组中的触摸信息。您必须使用 CloseTouchInputHandle 释放此句柄,或将消息传递给 DefWindowProc
进行清理。
TOUCHINPUT
结构包含许多元素。为了简洁起见,这里将屏幕坐标转换为我们应用程序的客户端坐标。如果“触摸”到下载项,则选择它。
LRESULT TouchHandler(HWND hh,UINT mm,WPARAM ww,LPARAM ll)
{
// Get the touching
int ni = LOWORD(ww);
TOUCHINPUT* ti = new TOUCHINPUT[ni + 1];
if (!GetTouchInputInfo((HTOUCHINPUT)ll,ni + 1,ti,sizeof(TOUCHINPUT)))
{
delete[] ti;
return DefWindowProc(hh,mm,ww,ll);
}
// Process Messages
for(int i = 0 ; i < ni ; i++)
{
LONG x = ti[i].x;
LONG y = ti[i].y;
// Convert these /100 as MSDN says
x /= 100;
y /= 100;
// Convert to client
POINT p = {x,y};
ScreenToClient(hh,&p);
// Select downloads
for(unsigned int i = 0 ; i < Downloads.size() ; i++)
{
if (InRect(p.x,p.y,Downloads[i]->HitTest))
Downloads[i]->Selected = !Downloads[i]->Selected;
}
}
delete[] ti;
CloseTouchInputHandle((HTOUCHINPUT)ll);
return 0;
}
更复杂的消息包含有关移动方式的信息,例如 TOUCHEVENTF_DOWN
。
阅读更多
- Windows 触摸 API。
四、传感器和位置 API
这是什么?
传感器 API 是一个新的抽象 API,用于查询传感器(即可以从硬件源生成数据的设备)的值。传感器可以包括 GPS、光探测器、温度探测器、运动探测器、生物识别(指纹)以及其他设备。
您可以将传感器 API 视为一种“原始输入”方法,通过它可以从任何具有传感器驱动程序的设备获取信息,无论该设备是什么。
位置 API 是传感器 API 的一个简化版本,它从已安装的 GPS 传感器获取 PC 的位置。如果您只想了解 PC 的位置,而不需要更多数据,例如卫星位置、速度等(GPS 传感器也会返回这些数据),那么它会很有用。
由于传感器可以提供用户敏感数据,因此传感器默认是禁用的。应用程序可以请求使用传感器,这会导致控制面板消息提示用户允许该传感器。即使以管理员身份运行,您也无法通过编程方式启用传感器访问。用户还可以通过控制面板指定哪些应用程序可以访问选定的传感器。
要使用传感器,必须安装传感器驱动程序。SDK 附带了一个“虚拟光传感器”,您可以使用它来模拟光探测器传感器。您还可以使用我自己的 GPS 传感器驱动程序,该驱动程序用于通过来自任何可以通过串口(USB 或蓝牙)连接的 NMEA 兼容 GPS 的真实数据来测试传感器和位置 API。如果您没有实际的 GPS 硬件,此驱动程序还可以模拟您的位置。
该应用程序使用传感器 API 查询虚拟光传感器,并调整客户端区域的前景色和背景色(光线越强,字体显示越粗)。该应用程序使用位置 API 查询您的 PC 位置,然后可以显示地图(使用 Google 地图)以及您的下载目的地(IP 通过 http://www.hostip.info/ 免费服务转换为 GPS 坐标)。
快速步骤
#include <sensors.h>
和<sensorsapi.h>
#pragma comment(lib,"sensors.lib");
CoCreate
ISensorManager*- 实现 ISensorManagerEvents 并将其传递给 ISensorManager::SetEventSink(),以便在新传感器可用时收到通知。
- 使用 ISensorManager::GetSensorsByCategory() 枚举传感器。您可以使用 已知类别 或自定义 CLSID,或
SENSOR_CATEGORY_ALL
。使用返回的 ISensorCollection 循环并使用 GetAt() 获取所需的 ISensor。 - 实现 ISensorEvents,并使用 ISensor::SetEventSink 和 ISensor::SetEventInterest,以便在传感器状态或传感器数据更改时收到通知。传感器被移除时也会收到通知。
- 使用 ISensor::GetData 获取 ISensorDataReport 以查询传感器数据。
传感器值的含义取决于传感器类型。有一些 预定义 的类型和类别可以使用,但也可以是任何类别和类型 CLSID,只要您知道如何解释它。对于自定义传感器驱动程序项目,您可以查看我自己的 3DConnexion 传感器 驱动程序,它映射为传感器和 3D 鼠标 3DConnexion 设备。
逐步光线查询(为简洁起见,已删除错误处理)
// Sensor Manager Events Implementation
class MySensorManagerEvents : public ISensorManagerEvents
{
private:
unsigned long ref;
public:
MySensorManagerEvents()
{
ref = 0;
AddRef();
}
// IUnknown
....
// ISensorManagerEvents
HRESULT __stdcall OnSensorEnter(
ISensor *pSensor,
SensorState state)
{
InvalidateRect(MainWindow,0,0);
UpdateWindow(MainWindow);
return S_OK;
}
};
MySensorManagerEvents sme;
// Sensor Events Implementation
class MySensorEvents : public ISensorEvents
{
private:
unsigned long ref;
public:
MySensorEvents()
{
ref = 0;
AddRef();
}
// IUnknown
...
// Sensor Events
HRESULT __stdcall OnEvent(ISensor *pSensor,
REFGUID eventID,IPortableDeviceValues *pEventData)
{
return S_OK;
}
HRESULT __stdcall OnDataUpdated(ISensor *pSensor,
ISensorDataReport *pNewData)
{
SensorDataUpdate(pSensor,pNewData);
return S_OK;
}
HRESULT __stdcall OnLeave(REFSENSOR_ID sensorID)
{
// Free sensors if released
if (sensorID == LightSensorID)
{
LightSensor->Release();
LightSensor = 0;
}
InvalidateRect(MainWindow,0,0);
UpdateWindow(MainWindow);
return S_OK;
}
HRESULT __stdcall OnStateChanged(ISensor *pSensor,SensorState state)
{
SensorDataUpdate(pSensor,0);
return S_OK;
}
};
MySensorEvents ses;
// Load the sensor
CoCreateInstance(CLSID_SensorManager,0,CLSCTX_ALL,
__uuidof(ISensorManager),(void**)&sm);
sm->SetEventSink(&sme);
GUID pguid[2];
pguid[0] = SENSOR_EVENT_DATA_UPDATED;
// Check to find light sensor
ISensorCollection* ic = 0;
sm->GetSensorsByCategory(SENSOR_CATEGORY_LIGHT,&ic);
ULONG pC = 0;
ic->GetCount(&pC);
if (pC)
{
// Get the first one
ic->GetAt(0,&LightSensor);
if (LightSensor)
{
LightSensor->GetID(&LightSensorID);
hr = LightSensor->SetEventSink(&ses);
hr = LightSensor->SetEventInterest(pguid,1);
}
}
// Do we have a light sensor?
if (LightSensor)
{
// Get the value
ISensorDataReport* d = 0;
LightSensor->GetData(&d);
if (!d) error(...)
// no data duh
PROPVARIANT pv;
PropVariantInit(&pv);
pv.vt = VT_R4;
d->GetSensorValue(SENSOR_DATA_TYPE_LIGHT_LEVEL_LUX,&pv);
d->Release();
// pv.fltVal has the LUX value
}
当您收到 OnLeave
回调时,您必须释放任何 ISensor
接口。
逐步位置查询(为简洁起见,已删除错误处理)
// Create the ILocation
ILocation* lm = 0;
CoCreateInstance(CLSID_Location,0,CLSCTX_ALL,__uuidof(ILocation),(void**)&lm);
ILocationReport* lmr = 0;
IID REPORT_TYPES[] = { IID_ILatLongReport }; // We want a XYZ report
// Ask permissions. If result is E_ACCESSDENIED,
// then the user doesn't allow our app to use the location API.
hr = lm->RequestPermissions(MainWindow,REPORT_TYPES,1,TRUE);
LOCATION_REPORT_STATUS status = REPORT_NOT_SUPPORTED;
hr = lm->GetReportStatus(IID_ILatLongReport, &status);
if (status == REPORT_RUNNING)
hr = lm->GetReport(__uuidof(ILatLongReport),&lmr);
// We must have a running sensor to get valid report.
if (lmr)
{
ILatLongReport* llr = 0;
hr = lmr->QueryInterface(__uuidof(ILatLongReport),(void**)&llr);
if (llr)
{
DOUBLE xx = 0,yy = 0,zz = 0;
llr->GetLongitude(&xx);
llr->GetLatitude(&yy);
llr->GetAltitude(&zz);
// xx,yy,zz have the XYZ of the location
llr->Release();
}
lmr->Release();
}
lm->Release();
非常重要的一点是,ILocation
接口是易变的。也就是说,您不能获取 ILocation
并在应用程序中一直使用它,而传感器信息可能会发生变化。为了安全起见,您应该在不需要时释放 ILocation
,并在再次需要位置信息时重新实例化它。
您可以使用我的 GPS 驱动程序 测试 ILocation
代码。
此驱动程序可以使用您的 GPS 硬件(例如蓝牙 GPS 设备),或者在没有硬件时模拟信息。
传感器驱动程序
传感器驱动程序是一个用户模式驱动程序 (UMDF),它向用户应用程序提供传感器信息。Windows 7 设备驱动程序工具包 提供了“SensorSkeleton
”驱动程序,您可以将其用作模板来实现您自己的传感器驱动程序。
阅读更多
- 传感器 和 位置 API 文档。
- 我的 CodeProject 中的 Sensor Explorer 项目。
- MSDN 中的 传感器驱动程序。
五、动画管理器
这是什么?
动画管理器 是一个新的 API,用于操作动画。它实际上不绘制任何东西。相反,它允许您指定要动画的变量(对象)和情节提要,其中包含要动画的变量和要使用的动画类型(有定义的动画,您也可以定义自己的动画)。最后,动画通过计时器(我们将在此处使用)或由应用程序本身触发来完成
因此,使用此 API,您只需指定要动画的内容和方式,然后返回应用于变量的数学结果,然后您可以使用它们来绘制对象。例如,如果您有一个基于 X、Y、Z 值旋转的 3D 立方体,您可以将这些值定义为线性、对数或其他预定义或自定义方式操作,然后您的回调函数将根据帧速率计时器与结果一起调用。
例如,此应用程序在下载期间绘制并动画一个正弦函数。因此它需要两个变量:“x”,它线性地从 0 变为 100,和“y”,它是一个从 -1 变为 1 的正弦函数。当值更新时(基于计时器),应用程序重新绘制正弦函数。
快速步骤
动画 API 是一个强大的 API,本文空间有限,只允许进行简要讨论。简而言之,对于计时器驱动的应用程序动画,您必须执行以下操作
CoCreate
IUIAnimationManager。CoCreate
IUIAnimationTimer。CoCreate
IUIAnimationTransitionLibrary。- 通过调用 IUIAnimationManager::SetTimerUpdateHandler() 将计时器附加到管理器,传递 IUIAnimationTimerUpdateHandler。您可以通过查询 IUIAnimationTimer 获取此接口。
- 实现 IUIAnimationManagerEventHandler 事件处理程序并将其传递给 IUIAnimationManager::SetTimerEventHandler。
- 通过调用 IUIAnimationManager::CreateAnimationVariable() 创建所有动画变量。有一个初始值,之后,值只能由转换设置。
- 通过调用 IUIAnimationManager::CreateStoryboard() 创建一个情节提要。情节提要是包含所有变量及其动画转换的存储。
- 调用
IUIAnimationTransitionLibrary
方法创建标准过渡。这里应用程序创建了一个线性过渡变量 (CreateLinearTransition),它表示正弦振荡中的 X 轴,以及一个正弦过渡变量 (CreateSinusoidalTransitionFromRange),它表示 Y 轴。 - 通过调用 IUIAnimationTimer::GetTime() 获取当前“动画时间”,然后将其传递给 IUIAnimationStoryboard::Schedule()。这会启动动画。
- 当变量更改时,您的事件处理程序 OnPreUpdate 会被调用。使用 IUIAnimationVariable::GetValue() 查询变量的值,然后使用它们根据其值绘制对象。
- 动画完成后,您将收到通知。重新创建对象以重新启动动画。
或者,您可以在动画中设置关键帧(时间位置),并指定在它们之间进行有限或无限循环。
逐步计时器动画(为简洁起见,已删除错误处理)
// Implementation of event handlers
class MyAnimationManagerEventHandler : public IUIAnimationManagerEventHandler
{
private:
unsigned long ref;
public:
MyAnimationManagerEventHandler()
{
ref = 1;
}
// IUnknown
...
// IUIAnimationManagerEventHandler
HRESULT __stdcall OnManagerStatusChanged(
UI_ANIMATION_MANAGER_STATUS newStatus,
UI_ANIMATION_MANAGER_STATUS previousStatus
)
{
if (newStatus == UI_ANIMATION_MANAGER_IDLE)
{
// Schedule the animation again
PostMessage(MainWindow,WM_USER + 1,0,0);
}
return S_OK;
}
};
class MyAnimationTimeEventHandler : public IUIAnimationTimerEventHandler
{
private:
unsigned long ref;
public:
MyAnimationTimeEventHandler()
{
ref = 1;
}
// IUnknown
...
// IUIAnimationTimerEventHandler
HRESULT __stdcall OnPostUpdate()
{
return S_OK;
}
HRESULT __stdcall OnPreUpdate()
// This is where we get the update values of the animated variables
{
// Request the y variable value
DOUBLE v1 = 0,v0 = 0;
if (amv[0])
amv[0]->GetValue(&v0);
if (amv[1])
amv[1]->GetValue(&v1);
void D2DrawAnimation(double,double);
D2DrawAnimation(v0,v1);
return S_OK;
}
HRESULT __stdcall OnRenderingTooSlow(UINT32 framesPerSecond)
{
return E_NOTIMPL;
}
};
MyAnimationTimeEventHandler matH;
MyAnimationManagerEventHandler maEH;
// Interface declarations
IUIAnimationManager* am = 0;
IUIAnimationTransitionLibrary* amtr = 0;
IUIAnimationStoryboard* amb = 0;
IUIAnimationTimer* amt = 0;
IUIAnimationVariable* amv[2] = {0,0};
// CoCreate elements
CoCreateInstance(CLSID_UIAnimationManager,0,CLSCTX_INPROC_SERVER,
__uuidof(IUIAnimationManager),(void**)&am);
CoCreateInstance(CLSID_UIAnimationTimer,0,CLSCTX_INPROC_SERVER,
__uuidof(IUIAnimationTimer),(void**)&amt);
CoCreateInstance(CLSID_UIAnimationTransitionLibrary,0,CLSCTX_INPROC_SERVER,
__uuidof(IUIAnimationTransitionLibrary),(void**)&amtr);
// Set the event handler
am->SetManagerEventHandler(&maEH);
// Timer Update Handler
IUIAnimationTimerUpdateHandler* amth = 0;
am->QueryInterface(__uuidof(IUIAnimationTimerUpdateHandler),(void**)&amth);
amt->SetTimerUpdateHandler(amth,UI_ANIMATION_IDLE_BEHAVIOR_DISABLE);
amth->Release();
// Set the timer event handler
amt->SetTimerEventHandler(&matH);
现在,我们需要创建一些变量,创建情节提要,并使用这些变量及其转换加载它。
// The Variables to animate (x,y of the point)
am->CreateAnimationVariable(0.0f,&amv[0]); // Start value 0
am->CreateAnimationVariable(0.0f,&amv[1]); // Start value 0
// Create the story board
am->CreateStoryboard(&amb);
// Create a linear and a sinusoidal transition for the variables
IUIAnimationTransition* trs = 0;
amtr->CreateSinusoidalTransitionFromRange(5,0.0f,1.0f,0.5f,
UI_ANIMATION_SLOPE_INCREASING,&trs);
amb->AddTransition(amv[1],trs);
trs->Release();
amtr->CreateLinearTransition(5,100,&trs);
amb->AddTransition(amv[0],trs);
trs->Release();
// Specify the time and start the animation
UI_ANIMATION_SECONDS se = 0;
if (SUCCEEDED(amt->GetTime(&se)))
hr = amb->Schedule(se);
amt->Enable();
在上述代码之后,动画将持续 5 秒(如我们在过渡中指定的那样)。X 变量从 0 开始线性变为 100,Y 变量从 0 开始以 0.5f 的周期(或 2Hz 频率)变为 1.0。应用程序使用这些值在下载进行时动画正弦函数,同时使用颜色透明度指示下载的百分比。
动画管理器允许您使用关键帧设置循环位置。有关更多信息,请参阅 创建情节提要。
阅读更多
六、Direct2D 和 DirectWrite
Direct2D 是一个功能强大的 ActiveX 硬件加速绘图 API,它取代了 GDI 和 GDI+,提供了增强的功能。应用程序通过 Direct2D 绘制其客户端区域;但是,此 API 的真正强大之处体现在绘制大量信息、实时滚动等应用程序中。这种应用程序的一个例子是我的 Turbo Play。Direct2D 还支持在硬件加速不可用时进行软件渲染。Direct2D 可以写入 HWND 或 HDC,允许您在需要时将其与 GDI 或 GDI+ 结合使用。此外,还提供了 DirectWrite 以向目标写入高质量文本。
引言
使用 Direct2D 的简短步骤指南是
#include <d2d1.h>
#include <dwrite.h>
- 使用 D2D1CreateFactory 创建 ID2D1Factory*。
- 如果要写入 HWND,请调用 ID2D1Factory::CreateHwndRenderTarget 创建 ID2D1HwndRenderTarget,或者调用 ID2D1Factory::CreateDCRenderTarget 获取 ID2D1DCRenderTarget* 写入设备上下文。
- 通过调用 DWriteCreateFactory() 创建 IDWriteFactory*。
- 使用渲染目标接口进行绘制。在绘制之前调用方法 BeginDraw(),使用
ID2D1HwndRenderTarget
或ID2D1DCRenderTarget
成员绘制数据,然后使用方法 Flush() 或 EndDraw() 结束绘制。Flush()
即使发生错误也会绘制结果,而EndDraw()
仅在没有发生错误时才绘制结果。检查EndDraw()
的返回值 - 您可能需要重新创建 Direct2D 对象。 - 当您的窗口大小调整时,调用方法 Resize() 更新 Direct2D 接口。
您应该使用 LoadLibrary()
/GetProcAddress
调用 CreateFactory
函数,以确保您的应用程序将在以前的操作系统版本中运行。
创建接口
ID2D1Factory* d2_f = 0;
ID2D1HwndRenderTarget* d2_RT = 0;
IDWriteFactory* d2_w = 0;
// Create the interface
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(ID2D1Factory),0,(void**)&d2_f);
// HWND Target Render
// First try hardware
D2D1_RENDER_TARGET_PROPERTIES def = D2D1::RenderTargetProperties();
def.type = D2D1_RENDER_TARGET_TYPE_HARDWARE;
RECT rc = {0};
GetClientRect(MainWindow,&rc);
d2_f->CreateHwndRenderTarget(def,
D2D1::HwndRenderTargetProperties(hh,D2D1::SizeU(
rc.right - rc.left,rc.bottom - rc.top)),&d2_RT);
if (!d2_RT)
{
// Try again
def.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
d2_f->CreateHwndRenderTarget(def,
D2D1::HwndRenderTargetProperties(hh,D2D1::SizeU(
rc.right - rc.left,rc.bottom - rc.top)),&d2_RT);
}
// DirectWrite
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),(IUnknown**)&d2_w);
Direct2D 颜色
Direct2D 颜色由 D2D1_COLOR_F
表示,其中包含从 0 到 1.0 的 ARGB 值。因此,要从经典的 ARGB 转换为 D2D1_COLOR_F
D2D1_COLOR_F GetD2Color(unsigned long c)
{
D2D1_COLOR_F cc;
cc.a = GetAValue(c)/255.0f;
if (cc.a == 0)
cc.a = 1.0f; // Cope for RGB colors only
cc.r = GetRValue(c)/255.0f;
cc.g = GetGValue(c)/255.0f;
cc.b = GetBValue(c)/255.0f;
return cc;
}
Direct2D 字体
Direct2D 字体由 IDWriteTextFormat*
表示。您通过调用 IDWriteFactory::CreateTextFormat()
,传递字体名称、大小和特征来创建此接口
FLOAT fs = 12.0f;
wstring fc = L"Tahoma";
IDWriteTextFormat* fo2d_n = 0;
d2_w->CreateTextFormat(fc.c_str(),0,DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,DWRITE_FONT_STRETCH_NORMAL,fs,L"",&fo2d_n);
Direct2D 画笔
Direct2D 支持各种风格的画笔。为了简洁起见,此应用程序使用经典的纯色画笔。
D2D1SolidColorBrush* GetD2SolidBrush(unsigned long c)
{
ID2D1SolidColorBrush* b = 0;
D2D1_COLOR_F cc = GetD2Color(c);
d2_RT->CreateSolidColorBrush(cc,&b);
return b;
}
请注意,没有“笔”;每个绘制形状的函数都接受笔触样式和宽度。
Direct2D 图像
向 Direct2D 提供图像最简单的方法是通过 Windows 图像组件。WIC 可以将 HBITMAP
转换为 IWICBitmap*
,然后我们使用渲染目标的 CreateBitmapFromWicBitmap()
将其转换为 Direct2D 喜欢的格式。如果直接转换失败,我们可以使用 WIC 将其转换为 32bppPBGRA
格式
void DrawImage(int x1,int y1,HBITMAP hB,float Op,bool HasAlpha,void** Cache)
{
BITMAP bo;
GetObject(hB,sizeof(bo),&bo);
WICBitmapAlphaChannelOption ao = WICBitmapUseAlpha;
IWICBitmap* wb = 0;
IWICImagingFactory* pImageFactory = 0;
CoCreateInstance(CLSID_WICImagingFactory,0,CLSCTX_ALL,
__uuidof(IWICImagingFactory),(void**)&pImageFactory);
pImageFactory->CreateBitmapFromHBITMAP(hB,0,ao,&wb);
ID2D1Bitmap* b = 0;
pRT->CreateBitmapFromWicBitmap(wb,0,&b);
if (!b)
{
// Convert it
IWICFormatConverter* spConverter = 0;
pImageFactory->CreateFormatConverter(&spConverter);
if (spConverter)
{
spConverter->Initialize(wb,GUID_WICPixelFormat32bppPBGRA,
WICBitmapDitherTypeNone,NULL,0.f,WICBitmapPaletteTypeMedianCut);
pRT->CreateBitmapFromWicBitmap(spConverter,0,&b);
spConverter->Release();
}
if (wb)
{
wb->Release();
wb = 0;
}
D2D1_RECT_F r;
r.left = (FLOAT)x1;
r.top = (FLOAT)y1;
r.right = (FLOAT)(x1 + bo.bmWidth);
r.bottom = (FLOAT)(y1 + bo.bmHeight);
pRT->DrawBitmap(b,r,Op);
// Release interfaces ...
}
使用此函数很容易,但在实践中,您应该将所有 HBITMAP
一次性转换为 ID2D1Bitmap*
,然后直接使用 DrawBitmap()
,以避免每次绘制位图时都产生不必要的开销。
Direct2D 形状
使用 ID2D1RenderTarget(它是 ID2D1HwndRenderTarget
和 ID2D1DCRenderTarget
的父类)公开的方法进行绘制
Draw
/FillEllipse
DrawLine
Draw
/FillRectangle
DrawText
此类支持许多其他形式的绘图,如层、路径、网格等。
Direct2D 多边形
以下示例展示了如何使用 DrawGeometry() 通过一组点创建多边形
void Polygon(POINT*p,int n,bool Close)
{
// Convert POINT to D2D1_POINT_2F
D2D1_POINT_2F* pt = new D2D1_POINT_2F[n];
for(int i = 0 ; i < n ; i++)
{
pt[i].x = (FLOAT)p[i].x;
pt[i].y = (FLOAT)p[i].y;
}
ID2D1SolidColorBrush* b = GetD2SolidBrush(c);
ID2D1PathGeometry* pg = 0;
ID2D1GeometrySink* pgs = 0;
pD2DFactory->CreatePathGeometry(&pg);
if (pg)
{
pg->Open(&pgs);
if (pgs)
{
D2D1_POINT_2F fb;
fb.x = (FLOAT)pt[0].x;
fb.y = (FLOAT)pt[0].y;
// Use D2D1_FIGURE_BEGIN_FILLED for filled
D2D1_FIGURE_BEGIN fg = D2D1_FIGURE_BEGIN_HOLLOW;
D2D1_FIGURE_END fe;
if (Close)
fe = D2D1_FIGURE_END_CLOSED;
else
fe = D2D1_FIGURE_END_OPEN;
pgs->BeginFigure(fb,fg);
for(int i = 1 ; i < n ; i++)
{
D2D1_POINT_2F fu;
fu.x = pt[i].x;
fu.y = pt[i].y;
pgs->AddLine(fu);
}
pgs->EndFigure(fe);
pgs->Close();
pgs->Release();
}
if (b)
pRT->DrawGeometry(pg,b,1);
pg->Release();
if (b)
b->Release();
delete[] pt;
}
DirectWrite 文本
绘制文本的步骤是
- 如上所述创建字体。
- 通过调用
IDWriteTextFormat::
SetParagraphAlignment 和 SetTextAlignment 设置文本对齐方式。 - 调用
ID2D1RenderTarget::
DrawText。
要测量文本尺寸,您必须创建 IDWriteTextLayout* (IDWriteFactory::
CreateTextLayout),它表示格式化文本的属性
POINT GetD2TextSize(IDWriteTextFormat* ffo,wchar_t* txt,int l = -1)
{
POINT p = {0};
// Create a layout
IDWriteTextLayout* lay = 0;
d2_w->CreateTextLayout(txt,l == -1 ? wcslen(txt) : l,ffo,1000,1000,&lay);
DWRITE_TEXT_METRICS m = {0};
float fx = lay->GetMaxWidth();
lay->GetMetrics(&m);
lay->Release();
// Save the metrics
int wi = (int)m.widthIncludingTrailingWhitespace;
if (m.widthIncludingTrailingWhitespace > (float)wi)
wi++;
int he = (int)m.height;
if (m.height > (float)he)
he++;
p.x = wi;
p.y = he;
return p;
}
阅读更多
七、Ribbon
本文的最后一章介绍了我认为 Win32 集合中最重要的附加 API。Windows 7 Ribbon 有效地将您的应用程序从“旧”样式转换为“新”样式。坦率地说,一个使用 Direct2D、传感器、任务栏列表等的应用程序不会被普通用户视为一项重大的升级;但一个取代旧应用程序工具栏和菜单的 Ribbon 肯定会引起注意。
基本上,Ribbon 是一个包含以下内容的区域
- 应用程序菜单
- 快速工具栏
- 可选的帮助按钮
- 选项卡
选项卡是一个包含其他控件组的区域。这些控件可以是按钮、复选框、下拉组合框、字体选择/颜色选择控件、下拉按钮等等。所有这些都存储在 XML 配置文件中,并使用 SDK 工具 UICC.EXE 编译成二进制文件。UICC.exe 还可以生成 .h 和 .rc 文件,以便将它们包含到您现有的 .rc 脚本中,从而加载 Ribbon 的图像。Ribbon 仅支持 32 位 BMP 图像。
Ribbon 选项卡可以永久显示,也可以选择性显示。选项卡和组支持“ApplicationMode
”,这只是一个二进制值,表示选项卡或组要显示的位。因此,ApplicationMode
== 0 的选项卡将始终显示,而 ApplicationMode
== 3 (11b) 的选项卡将在模式位 0 或 1 设置时显示。
Ribbon 可以包含更复杂的项目
选项卡也可以是“上下文相关的”。这类似于应用程序模式,但选项卡有一个特殊的焦点,以便用户注意到根据应用程序上下文有额外的可用内容
快速步骤
- 准备 Ribbon XML。您可以使用记事本编辑该文件,也可以使用工具(例如 VRC)为您生成二进制文件。有关 Ribbon 所需的 XML 格式的更多详细信息,请参阅我在 CodeProject 中的 Ribbon 文章。
CoCreate
IUIFramework
。- 实现 IUIApplication 并将其传递给 IUIFramework::Initialize()。
- 调用
IUIFramework::LoadUI()
加载 Ribbon 数据。 - 在
IUIApplication::
OnCreateUICommand() 回调中,实现 IUICommandHandler 接口(针对每个命令)并将其传回。 - 在
IUIApplication::
OnViewChanged() 中,检查typeID == UI_VIEWTYPE_RIBBON
和verb == UI_VIEWVERB_CREATE
,然后查询传入的IUnknown
接口以获取 IUIRibbon.* - 在
IUIApplication::OnViewChanged()
中,检查typeID == UI_VIEWTYPE_RIBBON
和verb == UI_VIEWVERB_SIZE
,然后调用IUIRibbon::
GetHeight() 获取 Ribbon 高度。每次 Ribbon 大小调整时,都会调用此回调。 - 每当 Ribbon 元素需要您的信息时,都会调用
IUICommandHandler::
UpdateProperty。如果您想强制更新属性,您可以调用IUIFramework::
InvalidateUICommand()。 - 每次发出命令(例如,按下按钮)时,都会调用
IUICommandHandler::
Execute。
IUIRibbon 接口还允许您将持久的 Ribbon 大小保存/加载到流中。
逐步 Ribbon 创建
命令处理器的实现,IUIApplication
的实现,Ribbon 的创建和初始化
// Implement a command handler for all commands
// IUICommandHandler for Implementation
class MyUICommandHandler : public IUICommandHandler
{
private:
class MyUIApplication* app;
UINT32 cmd;
UI_COMMANDTYPE ctype;
int refNum;
public:
MyUICommandHandler(class MyUIApplication* uapp,UINT32 c,UI_COMMANDTYPE ty)
{
refNum = 1;
app = uapp;
cmd = c;
ctype = ty;
}
// IUnknown
...
// IUICommandHandler
virtual HRESULT __stdcall UpdateProperty(UINT32 commandId,REFPROPERTYKEY key,
const PROPVARIANT *currentValue,PROPVARIANT *newValue)
{
// This is called when a property should be updated
if (key == UI_PKEY_Enabled)
{
// Enable or Disable commands here, lets say we want to disable Command ID 100
if (commandId == 100)
UIInitPropertyFromBoolean(key,FALSE,newValue);
else
UIInitPropertyFromBoolean(key,TRUE,newValue);
}
if (key == UI_PKEY_RecentItems)
{
// Display here the recent items.
// The ribbon wants a SAFEARRAY
// See RI.CPP for details on this
return 0;
}
if (key == UI_PKEY_ContextAvailable) // Contextual Tabs update
{
unsigned int gPR = (unsigned int)UI_CONTEXTAVAILABILITY_NOTAVAILABLE;
if (ShouldThatTabShow(commandID))
gPR = (unsigned int)UI_CONTEXTAVAILABILITY_ACTIVE;
UIInitPropertyFromUInt32(key, gPR, newValue);
}
return S_OK;
}
virtual HRESULT __stdcall Execute(UINT32 commandId,UI_EXECUTIONVERB verb,
const PROPERTYKEY *key,const PROPVARIANT *currentValue,
IUISimplePropertySet *commandExecutionProperties)
{
// This is called when there are actions
if (verb == UI_EXECUTIONVERB_EXECUTE)
{
if (ctype == UI_COMMANDTYPE_ACTION)
{
// Button Pressed
SendMessage(MainWindow,WM_COMMAND,commandId,0);
}
if (ctype == UI_COMMANDTYPE_RECENTITEMS)
{
if (!currentValue)
return 0;
// Recent Items
SendMessage(MainWindow,WM_COMMAND,15000 + currentValue->intVal,0);
}
// This is called on a font choosing
if (ctype == UI_COMMANDTYPE_FONT)
{
// See code for details
}
if (ctype == UI_COMMANDTYPE_COLORANCHOR)
{
// See code for details, this is called on color selection
return S_OK;
}
};
// Implement an IUIApplication
class MyUIApplication : public IUIApplication
{
private:
HWND hh; // Our Window
int refNum;
public:
MyUIApplication(HWND hP)
{
refNum = 1;
hh = hP;
}
// IUnknown
...
// IUIApplication callbacks
// These have to be implemented to handle notifications from a control
virtual HRESULT __stdcall OnCreateUICommand(UINT32 commandId,
UI_COMMANDTYPE typeID,IUICommandHandler **commandHandler)
{
// Create a command handler
if (!commandHandler)
return E_POINTER;
MyUICommandHandler * C = new MyUICommandHandler(this,commandId,typeID);
*commandHandler = (IUICommandHandler*)C;
return S_OK;
}
virtual HRESULT __stdcall OnDestroyUICommand(UINT32 commandId,
UI_COMMANDTYPE typeID,IUICommandHandler *commandHandler)
{
return S_OK;
// Do NOT release your MyUICommandHandler! It is released from the control!
}
virtual HRESULT __stdcall OnViewChanged(UINT32 viewId,UI_VIEWTYPE typeID,
IUnknown *view,UI_VIEWVERB verb,INT32 uReasonCode)
{
if (typeID == UI_VIEWTYPE_RIBBON && verb == UI_VIEWVERB_CREATE)
{
// This is where the ribbon is created, get a pointer to an IUIRibbon.
if (!u_r)
{
if (view)
view->QueryInterface(__uuidof(IUIRibbon),(void**)&u_r);
}
}
if (typeID == UI_VIEWTYPE_RIBBON && verb == UI_VIEWVERB_SIZE)
{
// Ribbon Resized, Update Window
UINT32 cy = 0;
if (u_r)
{
u_r->GetHeight(&cy);
LastRibbonHeight = cy;
Update(); // Main Window Resize
}
return S_OK;
}
return S_OK;
}
};
// CoCreate the ribbon
CoCreateInstance(CLSID_UIRibbonFramework,0,CLSCTX_ALL,
__uuidof(IUIFramework),(void**)&u_f);
// Initialize with the IUIApplication callback
u_app = new MyUIApplication(hh);
hr = u_f->Initialize(hh,u_app);
if (FAILED(hr))
{
u_f->Release();
u_f = 0;
return false;
}
// Load the Markup
hr = u_f->LoadUI(hAppInstance,_T("APPLICATION_RIBBON"));
if (FAILED(hr))
{
u_f->Release();
u_f = 0;
return false;
}
设置/查询属性
要设置命令属性,首先使用 InvalidateUICommand 使命令失效。这将导致 Ribbon 调用您的 UpdateProperty 方法,您可以在其中测试哪些值请求需要更改。例如,您需要检查 propertykey UI_PKEY_Enabled
以启用或禁用命令,UI_PKEY_RecentItems
以更改最近项目,UI_PKEY_ContextAvailable
以设置上下文选项卡等。此处列出了所有状态属性。
阅读更多
可能的错误?
这是我目前在 Windows 7 API 中发现的错误列表。也许,这些是我的代码的错误;请随意评论并提供建议。
ILocation
在重复使用或从回调中使用时会抛出异常。为了安全起见,在需要时立即使用并释放它。ILocation
不返回 Z(海拔),即使此属性已从传感器驱动程序正确返回。
限制
- Ribbon 无法通过应用程序动态生成或更改,因为需要 UICC。
- 一旦您对传感器对话框说“否”,传感器就会永久禁用应用程序。
- Ribbon 只接受 24 位位图。它应该允许 JPG、PNG 等。
- Ribbon 强制将图像放在包含 Ribbon 的同一模块中。这会迫使您将图像重新包含到您制作的每个本地化 DLL 中。
- Ribbon 不会在用户选择选项卡时通知您,并且您无法在运行时重新排序选项卡。
- 您无法通过编程方式从最近文档列表中删除项目。
- 要使最近项目出现在跳转列表中,您的应用程序必须已注册为该扩展的默认打开程序。例如,如果您打开 *.JXX 文件,则必须注册 JXX 扩展以使用您的应用程序打开,以便跳转列表包含该类型的最近文件。
致谢
特别感谢以下 Microsoft 员工与我合作测试各种 Windows 7 功能
- Ryan Demopoulos
- Nicolas Brun
- Kyle Marsh
- Jaime Rodriguez
历史
- 2010年1月6日:修复了一些错别字,更新了源代码。
- 2009年11月25日:首次发布。