为Ultimate Grid构建Oracle OCI数据源类,第一部分 - 将Ultimate Grid构建为外部DLL
一个三部分系列,
引言
本文是三部分系列文章的第一部分,介绍如何为Ultimate Grid控件构建自定义数据源类。自定义数据源将使用Oracle Call Interface (OCI)来使用Oracle数据库中的表为网格提供数据。
在第一部分中,我们将Ultimate Grid控件构建为外部DLL,以便将其包含在数据源类中。
第二部分将构建数据源类,重点是设置OCI环境,并定义一个能够从数据库获取元数据的类,以便能够访问任何表,而不管列的数量或数据类型如何。
在第三部分中,我们将把所有内容整合到一个示例应用程序中,该应用程序将使用第一部分构建的网格控件DLL和第二部分构建的数据源类,来开发一个能够显示Oracle数据库中任何表的应用程序。
背景
Ultimate Grid是一个功能强大的网格控件,能够显示来自各种数据源的数据。发行版中包含了几种数据源。然而,我的应用程序需要访问Oracle数据库。Ultimate Grid文档显示了如何构建自定义数据源。我担心我要访问的表的大小,所以我决定使用Oracle数据库随附的Oracle Call Interface (OCI)包。这给了我所需的控制和灵活性,以确保内存和服务器资源得到有效利用。
关注点
在为本文构建Ultimate Grid DLL时,我决定在最近构建的Windows 8.1机器上使用Visual Studio 2013进行尝试。虽然没有提供Visual Studio 2013项目,但我使用了2012年的项目,并在提示时进行了升级,一切运行良好。
使用VS2013还有一个额外的复杂问题,即多字节字符集 (MBCS) 库不再随Visual Studio一起安装,因为它们已被弃用。Ultimate Grid项目属性为其字符集指定了MBCS。我不想尝试将所有源文件从MBCS转换为Unicode。幸运的是,我之前在将我的Windows 7 PC上的第一个项目迁移到运行Visual Studio 2010时已经遇到了这个问题。
在MSDN网站上快速搜索后,我发现这些库仍然可以下载。有一个先决条件是需要安装补丁KB2883200,但我再次幸运地遇到了,我安装的Windows 8.1版本已经有了这个补丁。
解释这种情况的文章可以在这里找到。
这些库可以从这里下载。
Using the Code
在第一部分中,我们将Ultimate Grid控件构建为外部DLL,供数据源类和最终应用程序使用。
首先,如果您还没有Ultimate Grid包,请获取它。您可以从Code Project获取。
下载zip文件并解压到您选择的位置。在我的Windows 8.1 PC上,我使用了D:\apps\,所以我的安装目录是D:\apps\Ultimate Grid\。后续对文件位置的引用将假定此位置是Ultimate Grid组件的位置。
接下来,运行Visual Studio并打开BuildDLL项目。在我的情况下,它位于D:\apps\UltimateGrid\BuildDLL。如上所述,我使用了Visual Studio 2012项目并将其转换为2013。然而,我也成功地在32位Windows 7 PC上使用Visual Studio 2008项目,以及在64位Windows 7 PC上使用Visual Studio 2010项目构建了DLL。
我的Windows 8.1安装是64位的,所以我需要构建一个64位的DLL。要做到这一点,您必须将当前配置更改为x64。我的Visual Studio安装默认显示Win 32。点击解决方案平台列表框,选择“配置管理器…”
在“配置管理器”对话框中,单击“活动解决方案平台”列表框,然后选择“新建”。
在“新建解决方案平台”对话框中,确保新平台显示为x64。我从默认的Win32复制了设置。我还保留了“创建新项目平台”的勾选。
为了构建64位DLL,我们需要两个预处理器定义。点击菜单栏上的“项目”,然后点击“属性”。在“配置属性”对话框中,展开“配置属性”,然后展开“C/C++”,选择“预处理器”。在“预处理器定义”对话框中,点击列表框并选择“编辑”。添加这两个定义:_WIN64
和 _BUILD_UG_INTO_EXTDLL
。
_BUILD_UG_INTO_EXTDLL
特别值得关注。头文件ugdefine.h包含以下代码
//
#ifndef UG_CLASS_DECL
#ifdef _BUILD_UG_INTO_EXTDLL
#define UG_CLASS_DECL AFX_CLASS_EXPORT
#elif defined _LINK_TO_UG_IN_EXTDLL
#define UG_CLASS_DECL AFX_CLASS_IMPORT
#else
#define UG_CLASS_DECL
#endif
#endif
如果您查看网格的源代码,您会注意到D:\apps\UltimateGrid\Include中头文件里的类定义上带有UG_CLASS_DECL
修饰符。Ultimate Grid的作者这样做是为了让相同的头文件可以用于构建DLL,以及在将DLL用于其中的项目中。这一点很重要,因为我在构建自定义数据源的头文件时借鉴了这种技术,我们将在第二部分中看到。
如果此时构建项目,调试DLLUGMFCD.DLL应该在D:\apps\UlitmateGrid\x64\Debug中生成。我将DLL复制到D:\apps\UltimateGrid\DLLs,这就是lib文件放置的地方。然后,我将此目录添加到PATH
环境变量中,以便任何使用该DLL的应用程序都能找到它,如下面我们即将看到的。
一个快速测试应用程序
本文包含的示例项目构建了一个快速应用程序,只是为了确保我们构建的DLL能够正常工作。
由于我使用了VS2013来构建DLL,我创建了一个VS2013项目,但我也包含了一个VS2010版本。
这个测试应用程序不使用数据源,而是模仿Ultimate Grid包中包含的演示程序。OnSetup()
函数用于硬编码Scott模式中Oracle EMP表的一些数据。
我首先构建了一个新的Visual Studio项目,选择MFC作为类型,并使用了文档/视图架构。这个小应用程序不会使用文档类,但在我们进行到第三部分时,我们将使用文档类来控制数据源,所以我在这里演示如何在文档/视图应用程序中使用网格。
同样,我必须进入“项目”->“属性”并更改字符集为MBCS。
MainFrm.h和MainFrm.cpp文件也有代码用于使用注册表存储和检索窗口位置设置,以保持主应用程序窗口的状态。这是我自MFC 2.0以来一直在使用的代码。
在Mainfrm.h中,我添加了一个成员变量。我的目的是将其设为一个菜单选项,但我从不想关闭它,所以从未真正实现过菜单项。
// Attributes
public:
BOOL m_bSaveSettings;
以下代码放在MainFrm.cpp的PreCreateWindow
函数中。它使用注册表项 HKEY_CURRENT_USER\SOFTWARE\MyStuff\UGApp1\Settings来存储值。
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
DWORD ulOptions = REG_OPTION_NON_VOLATILE;
char SubKey[] = "Software\\MyStuff\\UGApp1\\Settings";
HKEY hWinlogon;
LONG result;
DWORD dwTotSize = 0;
DWORD dwDisp;
DWORD dwType = REG_DWORD;
DWORD dwSavePos = 1;
VALENT ValEntry[5];
char DataBuf[5 * sizeof(DWORD)];
char top[] = "top";
char left[] = "left";
char height[] = "height";
char width[] = "width";
char savepos[] = "savepos";
char RegClass[] = "REG_DWORD";
char szBuffer[100];
ValEntry[0].ve_valuename = top;
ValEntry[1].ve_valuename = left;
ValEntry[2].ve_valuename = height;
ValEntry[3].ve_valuename = width;
ValEntry[4].ve_valuename = savepos;
dwTotSize = 5 * sizeof(DWORD);
result = RegCreateKeyEx(HKEY_CURRENT_USER, SubKey, 0, RegClass, ulOptions,
KEY_ALL_ACCESS, NULL, &hWinlogon, &dwDisp);
if (result == ERROR_SUCCESS)
{
if (dwDisp == REG_OPENED_EXISTING_KEY)
{
result = RegQueryMultipleValues(hWinlogon, ValEntry, 5, DataBuf, &dwTotSize);
if (result == ERROR_SUCCESS)
{
cs.cx = *((LPDWORD)ValEntry[3].ve_valueptr);
cs.cy = *((LPDWORD)ValEntry[2].ve_valueptr);
cs.x = *((LPDWORD)ValEntry[1].ve_valueptr);
cs.y = *((LPDWORD)ValEntry[0].ve_valueptr);
if (*((LPDWORD)ValEntry[4].ve_valueptr) == 0)
m_bSaveSettings = FALSE;
else
m_bSaveSettings = TRUE;
}
else
{
sprintf(szBuffer,
"Reg Query Failed!\nCode: %ld\nDefault values will be used.", result);
AfxMessageBox(szBuffer);
cs.cx = 724;
cs.cy = 582;
cs.x = 136;
cs.y = 90;
m_bSaveSettings = TRUE;
}
}
else // dwDisp == REG_CREATED_NEW_KEY
{
cs.cx = 724; // width
cs.cy = 582; // height
cs.x = 136; // x coordinate for top/left
cs.y = 85; // Y coordinate for top/left
RegSetValueEx(hWinlogon, top, 0, dwType, (CONST BYTE *)&cs.y, sizeof(DWORD));
RegSetValueEx(hWinlogon, left, 0, dwType, (CONST BYTE *)&cs.x, sizeof(DWORD));
RegSetValueEx(hWinlogon, height, 0, dwType, (CONST BYTE *)&cs.cy, sizeof(DWORD));
RegSetValueEx(hWinlogon, width, 0, dwType, (CONST BYTE *)&cs.cx, sizeof(DWORD));
RegSetValueEx(hWinlogon, savepos, 0, dwType, (CONST BYTE *)&dwSavePos, sizeof(DWORD));
m_bSaveSettings = TRUE;
}
RegCloseKey(hWinlogon);
}
else
{
sprintf(szBuffer, "Registry Open Failed!\nCode: %ld", result);
AfxMessageBox(szBuffer);
m_bSaveSettings = FALSE;
}
if (CFrameWnd::PreCreateWindow(cs) == 0)
return FALSE;
cs.style &= ~(LONG)FWS_ADDTOTITLE;
return TRUE;
}
RegCreateKeyEx
函数会打开一个现有密钥,如果不存在则创建它。在第一次运行应用程序时,或者发生错误时,我使用一些默认设置。
cs.style &= ~(LONG)FWS_ADDTOTITLE;
语句用于稍后在视图类中修改主窗口标题,在确定用作网格源的表名后。
我们需要为WM_CLOSE
消息添加一个消息处理程序,并添加以下代码以将任何窗口位置的更改保存回注册表。
void CMainFrame::OnClose()
{
DWORD dwDataSize = (DWORD)sizeof(DWORD);
DWORD ulOptions = 0;
char SubKey[] = "Software\\MyStuff\\UGApp1\\Settings";
HKEY hWinlogon;
LONG result;
DWORD dwX = 0;
DWORD dwY = 0;
DWORD dwWidth = 0;
DWORD dwHeight = 0;
DWORD dwSave = 0;
DWORD dwTotSize = 0;
CRect WindRect;
char top[] = "top";
char left[] = "left";
char height[] = "height";
char width[] = "width";
char savepos[] = "savepos";
char szBuffer[100];
if (m_bSaveSettings)
{
dwSave = 1;
GetWindowRect(WindRect);
dwX = WindRect.left;
dwY = WindRect.top;
dwWidth = WindRect.right - WindRect.left;
dwHeight = WindRect.bottom - WindRect.top;
result = RegOpenKeyEx(HKEY_CURRENT_USER, SubKey, ulOptions,
KEY_WRITE, &hWinlogon);
if (result == ERROR_SUCCESS)
{
RegSetValueEx(hWinlogon, left, ulOptions, REG_DWORD,
(CONST BYTE*)&dwX, dwDataSize);
RegSetValueEx(hWinlogon, top, ulOptions, REG_DWORD,
(CONST BYTE*)&dwY, dwDataSize);
RegSetValueEx(hWinlogon, width, ulOptions, REG_DWORD,
(CONST BYTE*)&dwWidth, dwDataSize);
RegSetValueEx(hWinlogon, height, ulOptions, REG_DWORD,
(CONST BYTE*)&dwHeight, dwDataSize);
RegSetValueEx(hWinlogon, savepos, ulOptions, REG_DWORD,
(CONST BYTE*)&dwSave, dwDataSize);
RegCloseKey(hWinlogon);
}
else
{
sprintf(szBuffer, "Open Registry to save settings Failed!\nCode: %ld", result);
AfxMessageBox(szBuffer);
}
}
CFrameWnd::OnClose();
}
还需要修改项目属性。将D:\apps\Ultimate Grid\Include添加到VC++包含目录,并将D:\apps\Ultimate Grid\DLLs添加到VC++库目录。
添加预处理器定义 _LINK_TO_UG_IN_EXTDLL
。
在链接器输入属性中,添加附加依赖项UGMFCD.lib。
Ultimate Grid包包含一个骨架网格类实现。两个文件,MyCug.h和MyCug.cpp位于D:\apps\Ultimate Grid\Skel目录。这些文件会被复制到项目目录并添加到项目中,这使得入门非常容易。
我们只需要在OnSetup
函数中添加一些代码,我们就有数据可以显示在网格中了。我还展示了一些可以自定义网格外观的方法。有大量的灵活性和控制可用,但我这里所做的只是更改标题和列单元格的默认颜色。
/////////////////////////////////////////////////////////////////////////////
// OnSetup
// This function is called just after the grid window
// is created or attached to a dialog item.
// It can be used to initially setup the grid
void MyCug::OnSetup()
{
int x;
COLORREF cBlue = RGB(56, 40, 200);
COLORREF cYellow = RGB(255, 255, 0);
COLORREF cHeading = RGB(192, 192, 192);
CString temp;
CUGCell cell;
SetNumberRows(3);
SetNumberCols(8);
GetHeadingDefault(&cell);
cell.SetBackColor(cHeading);
SetHeadingDefault(&cell);
for (x = 0; x<8; x++)
{
GetColDefault(x, &cell);
cell.SetBackColor(cBlue);
cell.SetTextColor(cYellow);
SetColDefault(x, &cell);
}
// Column Headings
GetCell(0, -1, &cell);
cell.SetText("EMPNO");
SetCell(0, -1, &cell);
GetCell(1, -1, &cell);
cell.SetText("ENAME");
SetCell(1, -1, &cell);
GetCell(2, -1, &cell);
cell.SetText("JOB");
SetCell(2, -1, &cell);
GetCell(3, -1, &cell);
cell.SetText("MGR");
SetCell(3, -1, &cell);
GetCell(4, -1, &cell);
cell.SetText("HIREDATE");
SetCell(4, -1, &cell);
GetCell(5, -1, &cell);
cell.SetText("SAL");
SetCell(5, -1, &cell);
GetCell(6, -1, &cell);
cell.SetText("COMM");
SetCell(6, -1, &cell);
GetCell(7, -1, &cell);
cell.SetText("DEPTNO");
SetCell(7, -1, &cell);
cell.SetText("7369");
SetCell(0, 0, &cell);
cell.SetText("SMITH");
SetCell(1, 0, &cell);
cell.SetText("CLERK");
SetCell(2, 0, &cell);
cell.SetText("7902");
SetCell(3, 0, &cell);
cell.SetText("12/17/1980");
SetCell(4, 0, &cell);
cell.SetNumber(800);
SetCell(5, 0, &cell);
cell.SetNumber(0);
SetCell(6, 0, &cell);
cell.SetNumber(20);
SetCell(7, 0, &cell);
cell.SetText("7499");
SetCell(0, 1, &cell);
cell.SetText("ALLEN");
SetCell(1, 1, &cell);
cell.SetText("SALESMAN");
SetCell(2, 1, &cell);
cell.SetText("7698");
SetCell(3, 1, &cell);
cell.SetText("02/20/1981");
SetCell(4, 1, &cell);
cell.SetNumber(1600);
SetCell(5, 1, &cell);
cell.SetNumber(300);
SetCell(6, 1, &cell);
cell.SetNumber(30);
SetCell(7, 1, &cell);
cell.SetText("7499");
SetCell(0, 2, &cell);
cell.SetText("WARD");
SetCell(1, 2, &cell);
cell.SetText("SALESMAN");
SetCell(2, 2, &cell);
cell.SetText("7698");
SetCell(3, 2, &cell);
cell.SetText("02/22/1981");
SetCell(4, 2, &cell);
cell.SetNumber(1250);
SetCell(5, 2, &cell);
cell.SetNumber(500);
SetCell(6, 2, &cell);
cell.SetNumber(30);
SetCell(7, 2, &cell);
BestFit(0, 7, 3, UG_BESTFIT_TOPHEADINGS);
}
为了使用网格,我们需要在视图中定义一个MyCug
类的实例。我们需要在view.cpp文件中#include "MyCug.h"
,以及在app.cpp文件中,因为它有一个对视图类的引用。我们将类成员添加到view.cpp文件中。
// Attributes
public:
MyCug m_grid;
我们需要为WM_CREATE
和WM_SIZE
添加消息处理程序,并为OnInitialUpdate
编写一个重写。这是必需的代码
int CUGApp1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
m_grid.CreateGrid(WS_CHILD | WS_VISIBLE, CRect(0, 0, 0, 0), this, 9999);
return 0;
}
void CUGApp1View::OnInitialUpdate()
{
CView::OnInitialUpdate();
m_grid.OnSetup();
}
void CUGApp1View::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
m_grid.MoveWindow(0, 0, cx, cy);
}
修改OnDraw函数以设置主窗口标题
void CUGApp1View::OnDraw(CDC* /*pDC*/)
{
CUGApp1Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
pDoc->SetTitle("EmpDemo");
}
所以,如果我们构建并运行应用程序,并且Ultimate Grid DLL已正确构建,并且我们的应用程序能够找到它,我们将看到一个如下屏幕:
一旦您确定一切正常,您就可以构建Ultimate Grid DLL和测试应用程序的发布版本了。
构建测试应用程序的发布版本时,您需要将链接器依赖项更改为网格lib文件UGMFC.lib的发布版本。
将网格编译为外部DLL后,我们就为第二部分构建数据源类做好了准备。
历史
- 2014年5月18日:首次发布