3.如何实现动画?
动画的原理就是一帧一帧的画面按照时间轴向后移动,在骗过眼睛之后就成了动画,所以你得到动画最简单的方法就是按照一定间隔将不同图片一张一张绘制到屏幕上,虽然很简单,但是在编程中经常使用这种方法。有时简单的往往是最好的。
这里还有个技巧,比如将每张图片使用Photoshop中的运动滤镜模糊下,这样使用上面方法得到的动画会有种非常快速的感觉。也可以用类似的方法来用2D表现三维的事物,得到3D动画的效果。
还可以使用GIF动画的方式,比如在开机和关机时。以下封装的函数仅供参考,我没用心整理。
{
HANDLE hFile = CreateFile(strFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return FALSE;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
if ( (DWORD)-1 == dwFileSize )
{
CloseHandle(hFile);
return FALSE;
}
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);
if (hGlobal == NULL)
{
CloseHandle(hFile);
return FALSE;
}
LPVOID pvData = GlobalLock(hGlobal);
if (pvData == NULL)
{
GlobalUnlock(hGlobal);
CloseHandle(hFile);
return FALSE;
}
DWORD dwBytesRead = 0;
BOOL bRead = ReadFile(hFile, pvData, dwFileSize, &dwBytesRead, NULL);
GlobalUnlock(hGlobal);
CloseHandle(hFile);
if (!bRead)
{
return FALSE;
}
IStream* pStream = NULL;
if ( FAILED(CreateStreamOnHGlobal(hGlobal, TRUE, &pStream)) )
{
return FALSE;
}
if( NULL == pStream )
{
return FALSE;
} IImage *pImage = NULL;
RECT rc;
IImagingFactory *pImgFactory = NULL;
CoInitializeEx(NULL, COINIT_MULTITHREADED);
if ( !SUCCEEDED(CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void **)&pImgFactory)) )
{
return FALSE;
}
IImageDecoder* pDecoder = NULL;
UINT nCount = 0;
if ( !SUCCEEDED(pImgFactory->CreateImageDecoder(pStream, DecoderInitFlagNone, &pDecoder)) )
{
return FALSE;
}
pDecoder->GetFrameDimensionsCount(&nCount);
GUID *pDimensionIDs = (GUID*)new GUID[nCount];
pDecoder->GetFrameDimensionsList(pDimensionIDs,nCount);
TCHAR strGuid[39];
StringFromGUID2(pDimensionIDs[0], strGuid, 39);
UINT frameCount = 0;
pDecoder->GetFrameCount(&pDimensionIDs[0],&frameCount);
UINT iSize = 0;
pDecoder->GetPropertyItemSize(PropertyTagFrameDelay,&iSize);
BYTE* pBuff = new BYTE[iSize];
PropertyItem* pItem = (PropertyItem*)pBuff;
pDecoder->GetPropertyItem(PropertyTagFrameDelay,iSize,pItem);
int fCount = 0;
ImageInfo Info;
pImgFactory->CreateImageFromStream(pStream,&pImage);
pImage->GetImageInfo(&Info);
rc.left = rc.top = 0;
rc.right = Info.Width;
rc.bottom = Info.Height;
HDC tempDC;
HBITMAP hbmNew = NULL;
void * pv;
BITMAPINFO bmi = { 0 };
HBITMAP hbmOld = NULL;
tempDC = CreateCompatibleDC(NULL);
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = Info.Width;
bmi.bmiHeader.biHeight = Info.Height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = (SHORT) max(16, GetDeviceCaps(tempDC, BITSPIXEL));
bmi.bmiHeader.biCompression = BI_RGB;
hbmNew = CreateDIBSection(tempDC, &bmi, DIB_RGB_COLORS, &pv, NULL, 0);
hbmOld = (HBITMAP)SelectObject(tempDC, hbmNew);
pImage->Draw(tempDC, &rc, NULL);
pDecoder->SelectActiveFrame(&pDimensionIDs[0], ++fCount);
BitBlt(g_hdc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, tempDC, 0, 0, SRCCOPY);
delete []pBuff;
delete []pDimensionIDs;
pDecoder->Release();
pImage->Release();
pImgFactory->Release();
CoUninitialize();
return TRUE;
}
4.如何有较高的运行效率?
后面的内容会介绍到使用GDI这些“较高层次”的接口是很难有较高的运行效率。
但是可以使用一些技巧,比如“空间换取时间”。相信"Lazy Computation”你有听过,延迟处理这项任务直到真正需要的时候(在编程中我们也会经常用到,需要有这个意识。)这里使用的技巧有点恰恰相反的味道,把用户将来很可能用到的地方先处理好,然后储存起来,而并不是等到用户真正需要的时候才去处理。
比如使用Imaging COM组件绘制PNG图片时,每次都需要加载组件的库文件,然后卸载,界面可能要反复刷新,然后反复绘制PNG图片。这时可以考虑在程序启动的时候使用非界面主线程将绘制好的PNG图片保存起来(比如以Device Context的形式),界面刷新的时候仅仅是BitBlt到目标设备。BitBlt的效率是比较高的,如果仍然不能满足你的效率要求,可以考虑下面介绍的DirectDraw等技术。
上面的方法对于具有丰富开发经验的应该比较清楚,但是新手往往会忽略。在开发界面时我们要保证一个基本原则:想尽一切办法在现有的条件下提高界面响应用户的速度,界面要以用户为中心。所以开发时需要保持这个意识。
5.如何提高程序启动速度?
第4部分说过,为了提高运行效率,可以将常用的界面在程序启动时一起缓存到内存中,那么程序的启动时间会大大增加,如何解决这个问题?我的建议是UI主线程仅仅加载少量的用户启动后直接就能看到的界面,而另起一个子线程(叫A)用于加载其它界面,其它界面加载完之后这个子线程退出,当用户点击其它界面时,主线程如果发现子线程A并没有退出,说明其它界面还没有加载完,让用户等待。
这么设计的好处是,将最耗时的任务分摊出去,即能保证了用户快速看到界面,又能在之后的运行中有较高的效率。