WTL-Direct2D,DirectWrite,Windows Animation
Windows 7引入了不少有趣的新技术:Direct2D,DirectWrite,Windows Animation,还有Windows Media Foundation等等,在加上之前Windows Vista引入的Windows Image Component等技术,基本上把整个UI,多媒体框架都进行了翻新。
这几天放假无事,在看ATL/WTL,也顺带关注下Windows 7引入的这些新API。看了下SDK Sample,在介绍Windows Animation时有个有趣的小例子叫AppDrive,当用鼠标点击窗口时,窗口的背景色会非常平滑的变色。代码逻辑本身比较简单,就是纯Win32的代码看得比较费劲。于是无聊将其用WTL改写了下,代码精简了不少。
在写代码的过程中,越来越体会到ATL/WTL的强大,好用,可大大简化COM组件的编写和调用,非常适合进行Win32 API编程。
下面就把实现代码直接贴出来啦。
使用Windows Animation需要实现一个接口IUIAnimationManagerEventHandler,以下是这个接口的实现(与SDK Sample的实现方式不同):
#pragma once
#include "stdafx.h"
#include <UIAnimation.h>
[uuid("388E57E1-F20E-4E79-A732-35397AB8CC7C")]
__interface IAnimationNotifyWindow : public IUnknown
{
HRESULT __stdcall SetNotifyWindow(HWND hNotifyWindow);
};
class CAnimationEventHandler :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CAnimationEventHandler>,
public IUIAnimationManagerEventHandler,
public IAnimationNotifyWindow
{
public:
BEGIN_COM_MAP(CAnimationEventHandler)
COM_INTERFACE_ENTRY(IUIAnimationManagerEventHandler)
COM_INTERFACE_ENTRY(IAnimationNotifyWindow)
END_COM_MAP()
//IUIAnimationManagerEventHandler method
STDMETHOD(OnManagerStatusChanged)(
UI_ANIMATION_MANAGER_STATUS newStatus,
UI_ANIMATION_MANAGER_STATUS previousStatus)
{
if (m_NotifyWindow)
m_NotifyWindow.Invalidate();
return S_OK;
}
//IAnimationNotifyWindow method
STDMETHOD(SetNotifyWindow)(HWND hNotifyWindow)
{
ATLASSERT(::IsWindow(hNotifyWindow));
m_NotifyWindow = hNotifyWindow;
return S_OK;
}
private:
CWindow m_NotifyWindow;
};
这个CAnimationEventHandler类不仅实现了IUIAnimationManagerEventHandler,还实现了一个自定义接口IAnimationNotifyWindow,主窗口代码通过这个接口将其窗口句柄传递给CAnimationEventHandler类,以实现窗口通知(通知主窗口重绘)。
主窗口实现代码:
#pragma once
#include "stdafx.h"
#include <cstdlib>
#include <d2d1.h>
#include <d2d1helper.h>
#include <DWrite.h>
#include <wincodec.h>
#include <UIAnimation.h>
#include "AnimationEventHandler.h"
#pragma comment(lib,"d2d1.lib")
#pragma comment(lib,"dwrite.lib")
using D2D1::RenderTargetProperties;
using D2D1::HwndRenderTargetProperties;
using D2D1::LinearGradientBrushProperties;
using D2D1::SizeU;
using D2D1::SizeF;
using D2D1::ColorF;
using D2D1::Point2F;
using D2D1::Point2U;
using D2D1::RectF;
using D2D1::Matrix3x2F;
#pragma warning(push)
#pragma warning(disable:4244)
typedef CWinTraits<WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN> CMainWinTraits;
class CMainWindow :
public CWindowImpl<CMainWindow,CWindow,CMainWinTraits>
{
private:
//Direct2D interfaces
CComPtr<ID2D1Factory> m_spD2dFactory;
CComPtr<ID2D1HwndRenderTarget> m_spHwndRT;
CComPtr<ID2D1SolidColorBrush> m_spBkgndBrush;
CComPtr<ID2D1LinearGradientBrush> m_spTextBrush;
//DirectWrite interfaces
CComPtr<IDWriteFactory> m_spDWriteFactory;
CComPtr<IDWriteTextFormat> m_spTextFormat;
//UIAnimation interfaces
CComPtr<IUIAnimationManager> m_spIAniManager;
CComPtr<IUIAnimationTimer> m_spIAniTimer;
CComPtr<IUIAnimationTransitionLibrary> m_spIAniTransLib;
CComPtr<IUIAnimationVariable> m_spIAniVarRed; //red
CComPtr<IUIAnimationVariable> m_spIAniVarGreen; //greem
CComPtr<IUIAnimationVariable> m_spIAniVarBlue; //blue
public:
DECLARE_WND_CLASS(_T("WTL main window"))
BEGIN_MSG_MAP(CMainWindow)
MSG_WM_LBUTTONDOWN(OnLButtonDown)
MSG_WM_ERASEBKGND(OnEraseBkgnd)
MSG_WM_CREATE(OnCreate)
MSG_WM_DESTROY(OnDestroy)
MSG_WM_PAINT(OnPaint)
MSG_WM_SIZE(OnSize)
END_MSG_MAP()
int OnCreate(LPCREATESTRUCT lpCreateStruct)
{
SetWindowText(_T("Direct2D & DirectWrite & Windows Animation"));
CreateDeviceIndependentResource();
CreateDeviceResource();
return 0;
}
void OnDestroy()
{
PostQuitMessage(0);
}
void OnPaint(CDCHandle)
{
UI_ANIMATION_SECONDS timeNow;
IFR(m_spIAniTimer->GetTime(&timeNow));
IFR(m_spIAniManager->Update(timeNow));
Render();
UI_ANIMATION_MANAGER_STATUS status;
m_spIAniManager->GetStatus(&status);
if (status == UI_ANIMATION_MANAGER_BUSY)
{
Invalidate(FALSE);
}
}
void OnSize(UINT nType, CSize size)
{
if (m_spHwndRT)
m_spHwndRT->Resize(SizeU(size.cx,size.cy));
}
BOOL OnEraseBkgnd(CDCHandle dc)
{
//We have ereased the background
return TRUE;
}
void OnLButtonDown(UINT nFlags, CPoint point)
{
ChangeColor();
}
private:
void CreateDeviceIndependentResource()
{
//Direct2D
IFR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
&m_spD2dFactory));
//DirectWrite
IFR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&m_spDWriteFactory)));
//UIAnimation
IFR(m_spIAniManager.CoCreateInstance(CLSID_UIAnimationManager));
IFR(m_spIAniTimer.CoCreateInstance(CLSID_UIAnimationTimer));
IFR(m_spIAniTransLib.CoCreateInstance(CLSID_UIAnimationTransitionLibrary));
CComPtr<IUIAnimationManagerEventHandler> spIAniEventHandler;
IFR(CAnimationEventHandler::CreateInstance(&spIAniEventHandler));
CComPtr<IAnimationNotifyWindow> spIAniNotifyWindow;
IFR(spIAniEventHandler->QueryInterface(&spIAniNotifyWindow));
spIAniNotifyWindow->SetNotifyWindow(m_hWnd);
IFR(m_spIAniManager->SetManagerEventHandler(spIAniEventHandler));
IFR(m_spIAniManager->CreateAnimationVariable(1.0,&m_spIAniVarRed));
IFR(m_spIAniVarRed->SetLowerBound(0));
IFR(m_spIAniVarRed->SetUpperBound(1.0));
IFR(m_spIAniManager->CreateAnimationVariable(1.0,&m_spIAniVarGreen));
IFR(m_spIAniVarGreen->SetLowerBound(0));
IFR(m_spIAniVarGreen->SetUpperBound(1.0));
IFR(m_spIAniManager->CreateAnimationVariable(1.0,&m_spIAniVarBlue));
IFR(m_spIAniVarBlue->SetLowerBound(0));
IFR(m_spIAniVarBlue->SetUpperBound(1.0));
}
void CreateDeviceResource()
{
//Direct2D
CRect rc;
GetClientRect(&rc);
D2D1_SIZE_U size = SizeU(rc.Width(),rc.Height());
IFR(m_spD2dFactory->CreateHwndRenderTarget(
RenderTargetProperties(),
HwndRenderTargetProperties(m_hWnd,size),
&m_spHwndRT));
D2D1_COLOR_F color = ColorF(ColorF::Red);
IFR(m_spHwndRT->CreateSolidColorBrush(color,&m_spBkgndBrush));
CComPtr<ID2D1GradientStopCollection> spStopColl = NULL;
D2D1_GRADIENT_STOP stops2[] =
{
{0.25f,ColorF(ColorF::Red)},
{0.5f,ColorF(ColorF::Yellow)},
{0.75f,ColorF(ColorF::Blue)}
};
IFR(m_spHwndRT->CreateGradientStopCollection(
stops2,
ARRAYSIZE(stops2),
&spStopColl));
IFR(m_spHwndRT->CreateLinearGradientBrush(
LinearGradientBrushProperties(Point2F(0,0),Point2F(100,0)),
spStopColl,
&m_spTextBrush));
//DirectWrite
IFR(m_spDWriteFactory->CreateTextFormat(
_T("Courier New"),
nullptr,
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
48,
_T("zh-Hans"),
&m_spTextFormat));
m_spTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
m_spTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
}
void DiscardDeviceResource()
{
ATLTRACE("DiscardDeviceResource\n");
m_spTextBrush = NULL;
m_spBkgndBrush = NULL;
m_spHwndRT = NULL;
m_spTextFormat = NULL;
}
void Render()
{
if (!m_spHwndRT)
CreateDeviceResource();
HRESULT hr = S_OK;
m_spHwndRT->BeginDraw();
m_spHwndRT->SetTransform(Matrix3x2F::Identity());
//m_spHwndRT->Clear(ColorF(ColorF::White));
DOUBLE red=0, green=0, blue=0;
m_spIAniVarRed->GetValue(&red);
m_spIAniVarGreen->GetValue(&green);
m_spIAniVarBlue->GetValue(&blue);
m_spBkgndBrush->SetColor(ColorF(red,green,blue));
D2D1_SIZE_F size = m_spHwndRT->GetSize();
//Fill the background with randomly generated color
D2D1_RECT_F rect = RectF(0,0,size.width,size.height);
m_spHwndRT->FillRectangle(rect,m_spBkgndBrush);
//Rotate the render target and draw text
D2D1_POINT_2F center = Point2F(size.width/2,size.height/2);
FLOAT degree = red*360.0f;
m_spHwndRT->SetTransform(Matrix3x2F::Rotation(degree,center));
m_spTextBrush->SetEndPoint(Point2F(size.width,0));
CString text = _T("Direct2D&DirectWrite&Windows Animation");
m_spHwndRT->DrawText(
text,
text.GetLength(),
m_spTextFormat,
rect,
m_spTextBrush);
hr = m_spHwndRT->EndDraw();
if (hr == D2DERR_RECREATE_TARGET)
DiscardDeviceResource();
}
void ChangeColor()
{
DOUBLE red = ((DOUBLE)rand())/RAND_MAX;
DOUBLE green = ((DOUBLE)rand())/RAND_MAX;
DOUBLE blue = ((DOUBLE)rand())/RAND_MAX;
const UI_ANIMATION_SECONDS DURATION = 0.5;
const DOUBLE ACCEL_RATIO = 0.5;
const DOUBLE DECEL_RATIO = 0.5;
CComPtr<IUIAnimationStoryboard> spStoryBoard;
IFR(m_spIAniManager->CreateStoryboard(&spStoryBoard));
CComPtr<IUIAnimationTransition> spITransRed;
IFR(m_spIAniTransLib->CreateAccelerateDecelerateTransition(
DURATION,
red,
ACCEL_RATIO,
DECEL_RATIO,
&spITransRed));
CComPtr<IUIAnimationTransition> spITransGreen;
IFR(m_spIAniTransLib->CreateAccelerateDecelerateTransition(
DURATION,
green,
ACCEL_RATIO,
DECEL_RATIO,
&spITransGreen));
CComPtr<IUIAnimationTransition> spITransBlue;
IFR(m_spIAniTransLib->CreateAccelerateDecelerateTransition(
DURATION,
blue,
ACCEL_RATIO,
DECEL_RATIO,
&spITransBlue));
IFR(spStoryBoard->AddTransition(m_spIAniVarRed,spITransRed));
IFR(spStoryBoard->AddTransition(m_spIAniVarGreen,spITransGreen));
IFR(spStoryBoard->AddTransition(m_spIAniVarBlue,spITransBlue));
UI_ANIMATION_SECONDS timeNow;
IFR(m_spIAniTimer->GetTime(&timeNow));
spStoryBoard->Schedule(timeNow);
}
};
#pragma warning(pop)
与SDK Sample不同的是对IUIAnimationManagerEventHandler接口的实现以及通知窗口的设置,感觉用ATL风格的实现更加优雅,代码也要简洁很多:
CComPtr<IUIAnimationManagerEventHandler> spIAniEventHandler;
IFR(CAnimationEventHandler::CreateInstance(&spIAniEventHandler));
CComPtr<IAnimationNotifyWindow> spIAniNotifyWindow;
IFR(spIAniEventHandler->QueryInterface(&spIAniNotifyWindow));
spIAniNotifyWindow->SetNotifyWindow(m_hWnd);
IFR(m_spIAniManager->SetManagerEventHandler(spIAniEventHandler));
实现效果:
当用鼠标点击窗口时,窗口会平滑的变化背景色,而窗口中的文字则会以“动画”的形式旋转。