65.9K
CodeProject 正在变化。 阅读更多。
Home

为Ultimate Grid构建Oracle OCI数据源类,第一部分 - 将Ultimate Grid构建为外部DLL

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2014 年 5 月 22 日

CPOL

8分钟阅读

viewsIcon

15437

downloadIcon

559

一个三部分系列, 演示了如何为 Ultimate Grid 开发 Oracle Call Interface (OCI) 自定义数据源。

引言

本文是三部分系列文章的第一部分,介绍如何为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。点击解决方案平台列表框,选择“配置管理器…”

Configuration Manager Dialog

在“配置管理器”对话框中,单击“活动解决方案平台”列表框,然后选择“新建”。

Configuration Manager Dialog

在“新建解决方案平台”对话框中,确保新平台显示为x64。我从默认的Win32复制了设置。我还保留了“创建新项目平台”的勾选。

为了构建64位DLL,我们需要两个预处理器定义。点击菜单栏上的“项目”,然后点击“属性”。在“配置属性”对话框中,展开“配置属性”,然后展开“C/C++”,选择“预处理器”。在“预处理器定义”对话框中,点击列表框并选择“编辑”。添加这两个定义:_WIN64 _BUILD_UG_INTO_EXTDLL

Preprocessor Defines Dialog

_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.hMainFrm.cpp文件也有代码用于使用注册表存储和检索窗口位置设置,以保持主应用程序窗口的状态。这是我自MFC 2.0以来一直在使用的代码。

Mainfrm.h中,我添加了一个成员变量。我的目的是将其设为一个菜单选项,但我从不想关闭它,所以从未真正实现过菜单项。

// Attributes
public:
    BOOL m_bSaveSettings;

以下代码放在MainFrm.cppPreCreateWindow 函数中。它使用注册表项 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.hMyCug.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已正确构建,并且我们的应用程序能够找到它,我们将看到一个如下屏幕:

Sample Application Screen

一旦您确定一切正常,您就可以构建Ultimate Grid DLL和测试应用程序的发布版本了。

构建测试应用程序的发布版本时,您需要将链接器依赖项更改为网格lib文件UGMFC.lib的发布版本。

将网格编译为外部DLL后,我们就为第二部分构建数据源类做好了准备。

历史

  • 2014年5月18日:首次发布
© . All rights reserved.