Ultimate Grid 的 Oracle OCI 数据源类,第 3 部分,示例应用程序
一个三部分系列,
引言
本文是三部分系列文章的第三部分,展示了如何为 Ultimate Grid 控件构建一个自定义数据源类。自定义数据源将使用 Oracle Call Interface (OCI) 来使用 Oracle 数据库中的表为网格提供数据。
在第 1 部分中,我们将 Ultimate Grid 控件构建为一个外部 DLL,以便其他应用程序可以使用它。
在第 2 部分中,我们构建了数据源类。我们设置了 OCI 环境,定义了一个能够从数据库获取元数据的类,打开了数据库连接,获取了 select 列表,并使其可供 Ultimate Grid 控件使用。
在第 3 部分中,我们将所有内容整合到一个示例应用程序中,该应用程序将使用第 1 部分构建的网格控件 DLL 和第 2 部分构建的数据源类,来开发一个能够显示 Oracle 数据库中任何表的应用程序。
背景
本文附带的示例应用程序访问 scott/tiger 架构中的 EMP 表。使用提供的 Visual Studio 项目,我将展示如何修改代码以显示任何 Oracle 架构中的数据。
关注点
美化网格的外观
Ultimate Grid 是一个非常灵活且功能强大的控件,我想在这个示例应用程序中展示的一件事是网格的外观可以如何自定义。所有这些自定义都在 MyCug.cpp 实现文件中的 OnSetup()
函数中完成。
在示例应用程序中,我定义并使用了几种不同的颜色。颜色使用 RGB 宏定义。在我开发原始应用程序时,我尝试了几种不同的配色方案,并包含了所有定义的颜色,尽管它们并非全部在示例程序中使用。这里是我定义的颜色
COLORREF cHeading = GetSysColor(COLOR_BTNFACE);
COLORREF cBlack = RGB(0, 0, 0);
COLORREF cBlue = RGB(0, 0, 128);
COLORREF cGreen = RGB(0, 128, 0);
COLORREF cMilitaryGreen = RGB(64, 128, 128);
COLORREF cLtBlue = RGB(110, 180, 255);
COLORREF cWine = RGB(128, 0, 0);
COLORREF cYellow = RGB(255, 255, 0);
COLORREF cRed = RGB(255, 0, 0);
COLORREF cBuff = RGB(250, 230, 176);
COLORREF cGold = RGB(240, 194, 16);
COLORREF cWhite = RGB(255, 255, 255);
这些颜色定义用于设置默认的列标题文本和背景颜色,以及数据单元格的文本和背景颜色。
设置标题颜色时,首先定义一个单元格对象,然后通过调用 GetHeadingDefault(&cell)
将默认标题属性放入该单元格对象。然后使用 SetTextColor
和 SetBackColor
设置所需的颜色,然后再设置标题默认值。代码如下
CUGCell cell;
GetHeadingDefault(&cell);
cell.SetBackColor(cWine);
cell.SetTextColor(cYellow);
SetHeadingDefault(&cell);
设置数据单元格的颜色非常相似。首先,从数据源获取列数。然后将列默认值检索到单元格对象中。然后我们循环遍历列并设置文本和背景颜色,然后设置列默认值。
int cols = GetNumberCols();
for (x=0; x<cols; x++)
{
GetColDefault(x, &cell);
cell.SetTextColor(cYellow);
cell.SetBackColor(cBlue);
SetColDefault(x, &cell);
}
我在设置中使用的另一个语句是 SetUniformRowHeight
。此函数用于强制所有行具有相同的高度,即使用户调整了网格中的行大小。默认设置为“关闭”,因此我在 OnSetup()
例程中将其打开。
示例中的最后一个自定义是设置列宽。当我第一次在网格中显示 EMP 表时,JOB
和 HIREDATE
列会截断数据字段的几个像素。通过调用 BestFit()
函数来修复此问题。
BestFit
的签名如下
int CUGCtrl::BestFit(int startCol, int endCol, int calcRange, int flag);
计算最佳拟合有两种选择;基于列标题,或根据指定行数的数据计算平均宽度。使用列标题需要将标志参数设置为 UG_BESTFIT_TOPHEADINGS
。将标志设置为 UG_BESTFIT_AVERAGE
,并为 CalcRange
参数提供一个值,将使网格根据指定行数的平均值确定列宽。文档指出,行数越多,拟合效果越好。但这可能会影响性能。
这是未使用 BestFit()
时的网格外观

以下是适当调整列大小的代码
BestFit(0, 0, cols, UG_BESTFIT_TOPHEADINGS);
BestFit(1, 6, cols, UG_BESTFIT_AVERAGE);
BestFit(7, 7, cols, UG_BESTFIT_TOPHEADINGS);
完成大小调整后,网格现在看起来像这样
当我只进行平均计算时,EMPNO
和 DEPTNO
字段太窄,无法看到大部分列标题,因为数据值远小于列标题。我对这两个列使用了 UG_BESTFIT_TOPHEADINGS
,对所有其他列使用了 UG_BESTFIT_AVERAGE
。
此自定义已在示例应用程序中硬编码,因为我知道我将显示 EMP
表。如果您修改项目代码以显示其他表,您将需要调整这些行,或完全删除它们。
日期格式
我在第 2 部分提到了这一点,但值得重复的是,我构建的数据源类期望 Oracle DATE
字段和 Oracle TIMESTAMP
字段以特定方式格式化。这是通过设置环境变量 NLS_DATE_FORMAT
和 NLS_TIMESTAMP_FORMAT
来完成的。我使用了 'MM/DD/YYYY
' 的日期格式和 'MM/DD/YYYY HH24:MI:SS.FF
' 的时间戳格式。
如果您使用不同的日期格式,您可能需要修改为这两个数据值分配的内存量。如果您的日期格式大于 10
,或者您的时间戳格式大于 30
,那么您将不得不在 OciDtSrc.cpp 实现文件中的 get_result_list()
函数的 malloc()
调用中增加计数。有关如何构建数据源类的讨论,请参阅第 2 部分。
窗口持久性
在此示例应用程序中,我也使用了在第 1 部分中描述的代码来持久化主窗口的大小和位置。它位于 MainFrm.h 和 MainFrm.cpp 文件中,在对 PreCreateWindow()
和 OnClose()
的调用中。
Using the Code
使用示例应用程序
本文附带的 zip 文件包含三个文件;UGMFC.dll,第 1 部分构建的网格控件的发布版本;OciDtSrc.dll,第 2 部分构建的数据源类的发布版本;以及 UGApp2.exe,当前讨论的示例应用程序。
将这些文件解压到同一个目录并运行 UGApp2.exe。它应该会显示 scott/tiger 架构中的 EMP
表,前提是您的服务名为 ORCL
。
如果您的服务名不是 ORCL
,您仍然可以通过复制您拥有的服务来使用该示例,如果它访问包含 scott/tiger 架构的 Oracle 数据库实例。然后将地址名修改为 ORCL
,它应该可以工作。
例如,如果您的 Oracle 实例(安装了 scott 架构)在您的 TNSNAMES.ORA 文件中的条目如下所示
MYDB =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = myhost)(PORT = 1521))
(CONNECT_DATA =
(SERVICE_NAME = myservice)
)
)
那么复制该条目并将地址名更改为 ORCL
,如下所示,示例应用程序应该无需修改即可工作。
ORCL =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = myhost)(PORT = 1521))
(CONNECT_DATA =
(SERVICE_NAME = myservice)
)
)
您的第二个选择是修改代码并重新构建项目,如下所示。您可能无论如何都想这样做,以便显示对您更有意义的 Oracle 表。
使用项目文件
本文还包含一个 zip 文件,其中包含 Visual Studio 2013 项目文件,以及一个包含 Visual Studio 2010 项目文件的 zip 文件。如果您解压并使用相应版本的 Visual Studio 打开解决方案文件,您可以修改代码以满足您的需求。
这个示例应用程序是一个 SDI 程序,使用 MFC 的文档/视图架构。实例化 Ultimate Grid 控件的代码位于 CView
实现中,与本系列第 1 部分中使用的方法相同。
特别值得关注的区域将是文档类,因为您可以在此处修改用户 ID、密码、服务名和要访问的表名。
如果您查看 UGApp2Doc.cpp 中的 OnNewDocument()
例程,这些参数是在此处设置并传递给数据源的。
BOOL CUGApp2Doc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
int rc = 0;
CString msg;
DtSrc.SetPassword("scott", "tiger");
rc = DtSrc.Open("orcl", "emp");
if (rc != OCI_SUCCESS)
{
if (rc == OCI_ERROR)
msg = DtSrc.GetOciError();
else
if (rc < 9)
msg.Format("Missing Argument: %d", rc);
else
if (rc == 16)
msg.Format("Couldn't allocate memory for value buffers!");
else
msg.Format("Unknown Error!");
AfxMessageBox(msg);
return FALSE;
}
return TRUE;
}
数据源类的一个实例在 UGApp2Doc.h 中定义,如下所示
// Attributes
public:
COci DtSrc;
为了访问不同的服务、架构或表,请更改 SetPassword()
或 Open()
调用中的相应参数,您应该能够访问您有权访问的任何 Oracle 表。
我计划将来做的一件事是构建一个更灵活的用户界面。使用对话框获取用户 ID、密码和服务名并不难。然后可以使用 Oracle DescribeAny()
调用来填充 combobox
,其中包含所选架构中的表名。
摘要
再次强调,我只是触及了 Ultimate Grid 控件和 Oracle OCI 产品功能的表面。我可能还会进行另一个项目,即做一个 MDI 应用程序,其中可以使用多个文档对象,每个对象包含不同表的数据。网格控件还支持许多其他自定义功能。有一个易于实现的功能可以在用户右键单击网格时显示上下文菜单。我曾考虑过使用此功能从数据库中提取相关记录。例如,右键单击 EMP
表中的一行以显示相关的 DEPT
记录。
无论如何,我希望您像我一样享受阅读这一系列文章。我也很享受编写它们。
历史
- 2014 年 5 月 18 日:首次发布