最近项目中需要使用到水晶报表,在Win32工程中使用Direct2D来呈现,百度谷歌查找了很多资料发现水晶报表在VC中应用的相关资料不多,所以这里共享下给有需要的朋友。
首先导入水晶报表控件,因为VS2010开始就不再自带水晶报表控件,所以需要自己去下载安装crystal reports for visual studio 2010,百度一下应该就能找到,在VS2012中照样能用。安装之后找到安装目录下的craxddrt.dll,根据路径导入。因为使用的ADO连接的数据库,这里一并写上代码:
#import "..\\CrystalReports\SAP BusinessObjects Enterprise XI 4.0\win32_x86\craxddrt.dll" no_namespace rename ("FindText", "FindMyText") rename ("EnumFontFamilies", "MyEnumFontFamilies")
using namespace CrystalActiveXReportViewerLib13;
#import "C:\\Program Files\\Common Files\\System\\ado\\msado15.dll" no_namespace rename("EOF", "EndOfFile")
在这里我使用了ATL窗口来当容器导入和打开水晶报表,然后通过抓取ATL窗口DC生成D2DBitmap在我的工程中呈现,第一步创建ATL窗口打开*.rpt文件,并使用ADO连接现有数据库,
private
CAxWindow m_WindowContainer;
CComPtr m_pCrytalReporterViewer;
IApplicationPtr m_Application;
IReportPtr m_Report;
RECT m_rcAxWindow;
HWND m_hwndAtlAxWinMain;
_ConnectionPtr m_ADOConnectionPtr;
_RecordsetPtr m_ADORecordsetPtr;
_CommandPtr m_ADOCommandPtr;
HRESULT hr = S_FALSE;
try
{
m_rcAxWindow.left = 0;
m_rcAxWindow.top = 0;
m_rcAxWindow.right = m_rcAxWindow.left + ::GetSystemMetrics(SM_CXSCREEN)* 0.57f;
m_rcAxWindow.bottom = m_rcAxWindow.top + ::GetSystemMetrics(SM_CYSCREEN)* 0.57f;
AtlAxWinInit();
RECT rcClient = { m_rcAxWindow.left, m_rcAxWindow.top, m_rcAxWindow.right - m_rcAxWindow.left,m_rcAxWindow.bottom - m_rcAxWindow.top};
LPCTSTR p=L"CrystalReports13.ActiveXReportViewer.1";
m_hwndAtlAxWinMain = m_WindowContainer.Create(g_hViewWindow, rcClient, p, WS_POPUP|WS_VISIBLE );
if(!m_hwndAtlAxWinMain)
return FALSE;
MoveWindow(m_hwndAtlAxWinMain,m_rcAxWindow.left-1000, m_rcAxWindow.top, m_rcAxWindow.right - m_rcAxWindow.left,m_rcAxWindow.bottom - m_rcAxWindow.top,FALSE);
hr = m_WindowContainer.QueryControl(__uuidof(ICrystalReportViewer12),(void**)&m_pCrytalReporterViewer);
char tempRptPath[ARRAY_MAX] = {0};
WCHAR tempSrcFile[ARRAY_MAX]= {0};
WCHAR tempDBIP[ARRAY_MAX] ={0};
WCHAR tempDBName[ARRAY_MAX] ={0};
WCHAR tempDBUser[ARRAY_MAX] ={0};
WCHAR tempDBPWD[ARRAY_MAX] ={0};
strcat_s(tempRptPath,ARRAY_MAX,m_pDynamicUpdateParameters->m_strCRPPath);
MultiByteToWideChar(CP_ACP, 0, tempWebPath, -1,m_DataBaseSrcFile, ARRAY_MAX);
MultiByteToWideChar(CP_ACP, 0, m_pDynamicUpdateParameters->m_strCRPDBIP, -1,tempDBIP, ARRAY_MAX);
MultiByteToWideChar(CP_ACP, 0, m_pDynamicUpdateParameters->m_strCRPDBName, -1,tempDBName, ARRAY_MAX);
MultiByteToWideChar(CP_ACP, 0, m_pDynamicUpdateParameters->m_strCRPDBUser, -1,tempDBUser, ARRAY_MAX);
MultiByteToWideChar(CP_ACP, 0, m_pDynamicUpdateParameters->m_strCRPDBPWD, -1,tempDBPWD, ARRAY_MAX);
if (!FileExist(m_DataBaseSrcFile) )
{
return FALSE;
}
hr = m_ADOConnectionPtr.CreateInstance(__uuidof(Connection));///创建Connection对象
if(SUCCEEDED(hr))
{
m_ADOConnectionPtr->ConnectionTimeout = 0;
WCHAR temp[ARRAY_MAX] = _T("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=");
wcscat_s(temp,ARRAY_MAX,tempDBName);
hr = m_ADOConnectionPtr->Open(temp, tempDBUser, tempDBPWD, adModeUnknown);
}
if(SUCCEEDED(hr))
{
hr = m_ADOCommandPtr.CreateInstance(__uuidof(Command));
m_ADOCommandPtr->CommandTimeout = 5;
m_ADOCommandPtr->ActiveConnection = m_ADOConnectionPtr;
}
if(SUCCEEDED(hr))
{
hr = m_ADORecordsetPtr.CreateInstance(__uuidof(Recordset));
if(SUCCEEDED(hr))
hr = m_ADORecordsetPtr->Open("SELECT '价格(建议零售价)' FROM 产品",// 查询DemoTable表中所有字段
m_ADOConnectionPtr.GetInterfacePtr(), // 获取库接库的IDispatch指针
adOpenDynamic,
adLockOptimistic,
adCmdText);
}
if(m_pCrytalReporterViewer)
{
if(m_pMutexUpdateDB)
{
CCriticalSectionLock csviewport(*m_pMutexUpdateDB);
hr = m_Application.CreateInstance(__uuidof(Application));
m_Report = m_Application->OpenReport(_bstr_t(m_DataBaseSrcFile));
hr = m_Report->Database->SetDataSource(_variant_t((IDispatch*)m_ADORecordsetPtr));
m_pCrytalReporterViewer->put_ReportSource(m_Report);
m_pCrytalReporterViewer->put_DisplayToolbar(VARIANT_FALSE);
m_pCrytalReporterViewer->put_DisplayGroupTree(VARIANT_FALSE);
m_pCrytalReporterViewer->put_DisplayBorder(VARIANT_FALSE);
m_pCrytalReporterViewer->put_DisplayTabs(VARIANT_FALSE);
m_pCrytalReporterViewer->put_EnableDrillDown(VARIANT_FALSE);
m_pCrytalReporterViewer->put_EnableRefreshButton(VARIANT_FALSE);
m_pCrytalReporterViewer->RefreshEx(FALSE);
m_pCrytalReporterViewer->raw_ViewReport();
m_pCrytalReporterViewer->raw_Zoom(1);
}
}
catch(_com_error e)
{
CString errormessage;
errormessage.Format(L"连接数据库失败!\r\n错误信息:%s",e.ErrorMessage());
AfxMessageBox(errormessage);///显示错误信息
return FALSE;
}
tempRptPath为你的*.rpt文件路径,没有其他错误的话,你编辑好的rpt文件已经可以在ATL窗口打开了。
接下来我所做的就是抓取ATL窗口DC生成一张D2DBitmap,首先通过D3D设备创建一张D2DBitmap
if(m_pd3dDevice)
{
HRESULT hr = S_FALSE;
D3D11_TEXTURE2D_DESC texDesc;
texDesc.ArraySize = 1;
texDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
texDesc.CPUAccessFlags = 0;
texDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
texDesc.Height = m_rcAxWindow.bottom - m_rcAxWindow.top;
texDesc.Width = m_rcAxWindow.right - m_rcAxWindow.left;
texDesc.MipLevels = 1;
texDesc.MiscFlags = D3D11_RESOURCE_MISC_GDI_COMPATIBLE;
texDesc.SampleDesc.Count = 1;
texDesc.SampleDesc.Quality = 0;
texDesc.Usage = D3D11_USAGE_DEFAULT;
hr = m_pd3dDevice->CreateTexture2D(&texDesc, NULL, &m_pScreenTexture2D);
if(SUCCEEDED(hr))
{
if(m_pScreenTexture2D )
{
IDXGISurface1 *pDxgiSurface = NULL;
hr = m_pScreenTexture2D->QueryInterface(&pDxgiSurface);
if (SUCCEEDED(hr))
{
if(m_pID2D1Factory)
{
FLOAT dpiX, dpiY;
m_pID2D1Factory->GetDesktopDpi(&dpiX, &dpiY);
D2D1_RENDER_TARGET_PROPERTIES props =D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED),dpiX,dpiY
);
CreateSharedBitmapResources(texDesc.Width,texDesc.Height,pDxgiSurface);
}
else
{
return FALSE;
}
}
else
return FALSE;
}
}
else
{
if(NULL != g_hViewWindow)
{
MessageBox(NULL, L"Could not initialize Direct3D. CreateTexture2D ", L"Error", MB_OK|MB_TOPMOST);
}
return FALSE;
}
}
HRESULT CreateSharedBitmapResources(int nBmpWidth,int nBmpHeight,IDXGISurface1* pIDXGISurfaceBuffer)
{
HRESULT hResult = S_FALSE;
D2D1_PIXEL_FORMAT pixelFormat =
{
DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_IGNORE
};
FLOAT dpiX, dpiY;
m_pID2D1Factory->GetDesktopDpi(&dpiX, &dpiY);
D2D1_BITMAP_PROPERTIES bitmapProps =
{
pixelFormat,
dpiX,
dpiY
};
D2D1_SIZE_U bitmapSize =
{
nBmpWidth,
nBmpHeight
};
D2D1_BITMAP_PROPERTIES prop = D2D1::BitmapProperties(
pixelFormat,dpiX,dpiY);
if(m_pBitmap)
{
m_pBitmap->Release();
m_pBitmap = NULL;
}
hResult = m_spRT->CreateSharedBitmap(__uuidof(IDXGISurface1), pIDXGISurfaceBuffer, &bitmapProps, &m_pBitmap);
return hResult;
}
接下来我们需要做的就是实时更新数据,抓取更新后的窗口DC来呈现了。关键的地方来了,从网上找的刷新水晶报表时需要用到代码到了我的工程中怎么都不适用,刷新的时候老是会弹出刷新确认按钮,而且刷新时重连数据源,设报表等操作开销实在太大,影响到了D2D的呈现,所以我只有把刷新数据和抓取图片放到了辅线程中,辅线程调用了UpDateDataBase()和ViewTransformationToBmp()两个方法,网上代码中刷新就是直接用的Refresh(),但是这样会有确认提示对话框出现,当我改用RefreshEx(FALSE)的时候,对话框就没了。 GetBtimapThread为辅线程回调函数,10秒更新一次数据完全符合需求,刷新一次数据抓取一次DC。
void UpDateDataBase()
{
if(m_pMutexUpdateDB)
{
CCriticalSectionLock csviewport(*m_pMutexUpdateDB);
m_Report->DiscardSavedData();
m_Report->Database->SetDataSource(_variant_t((IDispatch*)m_ADORecordsetPtr));
m_pCrytalReporterViewer->RefreshEx(FALSE);
}
HRESULT ViewTransformationToBmp()
{
HRESULT hResult = S_FALSE;
if(m_pMutexGetbitmap)
{
CCriticalSectionLock d2dbitmap(*m_pMutexGetbitmap);
IDXGISurface1 *pBackBuffer = NULL;
if(m_pD2DDriver && m_pBitmap && m_pScreenTexture2D)
{
IDXGISurface1 *pBackBuffer = NULL;
m_pScreenTexture2D->QueryInterface(__uuidof(IDXGISurface1), (void**)&pBackBuffer);
if(pBackBuffer)
{
RECTL rt;
rt.left = m_rcAxWindow.left;
rt.right = m_rcAxWindow.right;
rt.top = m_rcAxWindow.top;
rt.bottom = m_rcAxWindow.bottom;
LONG lWidth = m_rcAxWindow.right-m_rcAxWindow.left;
LONG lHeight = m_rcAxWindow.bottom-m_rcAxWindow.top;
HDC hdc = NULL;
hResult = pBackBuffer->GetDC(TRUE, &hdc);
if((hdc)&&m_hwndAtlAxWinMain)
{
HDC ptempdc = ::GetDC(m_hwndAtlAxWinMain);
if(ptempdc)
{
::BitBlt(hdc,0,0,lWidth,lHeight,ptempdc,m_rcAxWindow.left,m_rcAxWindow.top,SRCCOPY);
}
ReleaseDC(m_hwndAtlAxWinMain,ptempdc);
}
hResult = m_pBitmap->CopyFromMemory(NULL,pBackBuffer,lWidth);
if(pBackBuffer)
{
pBackBuffer->ReleaseDC(NULL);
pBackBuffer = NULL;
}
}
}
}
return hResult;
}
DWORD WINAPI GetBtimapThread(LPVOID lpvoid)
{
CCrystalReports *pRPTParam = reinterpret_cast<CCrystalReports*>(lpvoid);
if (!pRPTParam)
return 0;
if (!pRPTParam->m_hGetBitmapThread)
{
return 0;
}
while(1)
{
pRPTParam->UpDateDataBase();
Sleep(5000);
pRPTParam->ViewTransformationToBmp();
Sleep(5000);
}
return 0L;
}
附图