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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (72投票s)

2009年11月25日

CPOL

22分钟阅读

viewsIcon

162037

downloadIcon

2681

七项新编程技术的演示。

w7dl.png

引言

欢迎来到 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 可执行文件

所需软件

可选/有用的软件

  • 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 中使用。

一、任务栏扩展

它们是什么?

应用程序使用这些扩展来

  • 指定任务栏右键单击(跳转列表)的最近/常用项目。
  • 显示工具栏以操作应用程序而无需切换到应用程序。
  • 将任务栏用作进度条。

tbar_recent.png

tbar_toolbar.png

tbar_progress.png

图 1.1:跳转列表
图 1.2:工具栏
图 1.3:进度条

重要提示 #1:所有任务栏操作都必须在您的应用程序知道任务栏按钮已创建后发生。调用 RegisterWindowMessage(L"TaskbarButtonCreated") 以获取消息 ID,当您在循环中收到此消息时,然后执行任务栏操作。不这样做将花费您数小时的调试时间,就像发生在我身上一样 :)

重要提示 #2:如果您以管理员身份运行应用程序,则无法使用操作工具栏,因为 Explorer 在中等完整性下运行,因此无法向您的应用程序发送消息。如果您确实希望 Explorer 在高完整性模式下能够与您的应用程序通信,则必须对 WM_COMMANDRegisterWindowMessage(L"TaskbarButtonCreated") 返回的消息使用 ChangeWindowMessageFilterEx

快速步骤

逐步操作最近项目(为简洁起见,已删除错误处理)

有两个已知类别您不需要定义任何内容,即最近项目和常用项目。这些项目必须是这些类别的文件名。如果您想添加任何其他类型的类别,您必须实现自己的 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();

阅读更多

二、库

什么是库?

是一种告诉 Windows 7 您的文件存储在哪里f的方式。“我的文档”文件夹的问题是只有一个;用户可能希望将重要文件存储在硬盘的各个位置。库告诉 Windows 您的收藏集在哪里,以便可以轻松地对其进行索引和搜索。因此,一个库可以包含许多文件,它们被分类为单个文件夹中的文件,而实际上它们位于硬盘的不同目录中。它基本上是多个目录的容器;这降低了应用程序的复杂性 - 例如,一个需要在文件更改时收到通知的应用程序,现在只需要监视库对象,该对象会自动监视其包含的所有项目。

libr_sample.png

图 2.1:我们的“W7Downloads”库包含两个文件夹,W7DL 和 G:\TEMP。

例如,一个人可能希望将他们从克里特岛拍摄的所有照片存储在特殊位置(取决于他们何时访问该岛),但为了能够一次性搜索所有照片,他们只需定义一个包含所有照片的库,然后对这些照片进行搜索、索引和排序,就好像它们属于单个目录一样。

由于库是 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-&gt;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();

要添加更多目录,我们使用相同的函数。

阅读更多

三、触摸 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

阅读更多

四、传感器和位置 API

这是什么?

传感器 API 是一个新的抽象 API,用于查询传感器(即可以从硬件源生成数据的设备)的值。传感器可以包括 GPS、光探测器、温度探测器、运动探测器、生物识别(指纹)以及其他设备。

您可以将传感器 API 视为一种“原始输入”方法,通过它可以从任何具有传感器驱动程序的设备获取信息,无论该设备是什么。

位置 API 是传感器 API 的一个简化版本,它从已安装的 GPS 传感器获取 PC 的位置。如果您只想了解 PC 的位置,而不需要更多数据,例如卫星位置、速度等(GPS 传感器也会返回这些数据),那么它会很有用。

由于传感器可以提供用户敏感数据,因此传感器默认是禁用的。应用程序可以请求使用传感器,这会导致控制面板消息提示用户允许该传感器。即使以管理员身份运行,您也无法通过编程方式启用传感器访问。用户还可以通过控制面板指定哪些应用程序可以访问选定的传感器。

sensor_permission.png

图 4.1:应用程序尝试访问传感器时显示的权限对话框。

要使用传感器,必须安装传感器驱动程序。SDK 附带了一个“虚拟光传感器”,您可以使用它来模拟光探测器传感器。您还可以使用我自己的 GPS 传感器驱动程序,该驱动程序用于通过来自任何可以通过串口(USB 或蓝牙)连接的 NMEA 兼容 GPS 的真实数据来测试传感器和位置 API。如果您没有实际的 GPS 硬件,此驱动程序还可以模拟您的位置。

该应用程序使用传感器 API 查询虚拟光传感器,并调整客户端区域的前景色和背景色(光线越强,字体显示越粗)。该应用程序使用位置 API 查询您的 PC 位置,然后可以显示地图(使用 Google 地图)以及您的下载目的地(IP 通过 http://www.hostip.info/ 免费服务转换为 GPS 坐标)。

快速步骤

传感器值的含义取决于传感器类型。有一些 预定义 的类型和类别可以使用,但也可以是任何类别和类型 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 代码。

sensor_location.png

图 4.2:GPSDirect 驱动程序。

此驱动程序可以使用您的 GPS 硬件(例如蓝牙 GPS 设备),或者在没有硬件时模拟信息。

传感器驱动程序

传感器驱动程序是一个用户模式驱动程序 (UMDF),它向用户应用程序提供传感器信息。Windows 7 设备驱动程序工具包 提供了“SensorSkeleton”驱动程序,您可以将其用作模板来实现您自己的传感器驱动程序。

阅读更多

五、动画管理器

这是什么?

动画管理器 是一个新的 API,用于操作动画。它实际上不绘制任何东西。相反,它允许您指定要动画的变量(对象)和情节提要,其中包含要动画的变量和要使用的动画类型(有定义的动画,您也可以定义自己的动画)。最后,动画通过计时器(我们将在此处使用)或由应用程序本身触发来完成

anim_sample.png

图 5.1:下载时的正弦动画。

因此,使用此 API,您只需指定要动画的内容和方式,然后返回应用于变量的数学结果,然后您可以使用它们来绘制对象。例如,如果您有一个基于 X、Y、Z 值旋转的 3D 立方体,您可以将这些值定义为线性、对数或其他预定义或自定义方式操作,然后您的回调函数将根据帧速率计时器与结果一起调用。

例如,此应用程序在下载期间绘制并动画一个正弦函数。因此它需要两个变量:“x”,它线性地从 0 变为 100,和“y”,它是一个从 -1 变为 1 的正弦函数。当值更新时(基于计时器),应用程序重新绘制正弦函数。

快速步骤

动画 API 是一个强大的 API,本文空间有限,只允许进行简要讨论。简而言之,对于计时器驱动的应用程序动画,您必须执行以下操作

或者,您可以在动画中设置关键帧(时间位置),并指定在它们之间进行有限或无限循环。

逐步计时器动画(为简洁起见,已删除错误处理)

// 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 的简短步骤指南是

您应该使用 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(它是 ID2D1HwndRenderTargetID2D1DCRenderTarget 的父类)公开的方法进行绘制

  • 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 文本

绘制文本的步骤是

要测量文本尺寸,您必须创建 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 肯定会引起注意。

rib_sample.png

图 7.1:Visual Ribbon Creator 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 可以包含更复杂的项目

rib_paint.png

图 7.2:MS Paint Ribbon。

选项卡也可以是“上下文相关的”。这类似于应用程序模式,但选项卡有一个特殊的焦点,以便用户注意到根据应用程序上下文有额外的可用内容

rib_contextual.png

图 7.3:Turbo Play 上下文相关的 Ribbon 仅在选择 MIDI 音乐曲目时显示,以显示乐谱编辑器。

快速步骤

  • 准备 Ribbon XML。您可以使用记事本编辑该文件,也可以使用工具(例如 VRC)为您生成二进制文件。有关 Ribbon 所需的 XML 格式的更多详细信息,请参阅我在 CodeProject 中的 Ribbon 文章
  • CoCreate IUIFramework
  • 实现 IUIApplication 并将其传递给 IUIFramework::Initialize()
  • 调用 IUIFramework::LoadUI() 加载 Ribbon 数据。
  • IUIApplication::OnCreateUICommand() 回调中,实现 IUICommandHandler 接口(针对每个命令)并将其传回。
  • IUIApplication::OnViewChanged() 中,检查 typeID == UI_VIEWTYPE_RIBBONverb == UI_VIEWVERB_CREATE,然后查询传入的 IUnknown 接口以获取 IUIRibbon.*
  • IUIApplication::OnViewChanged() 中,检查 typeID == UI_VIEWTYPE_RIBBONverb == 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 以设置上下文选项卡等。此处列出了所有状态属性。

阅读更多

  • Ribbon API 文档。
  • 我在 CodeProject 中的 Ribbon 文章

可能的错误?

这是我目前在 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日:首次发布。
© . All rights reserved.