VC++在MFC下调用EXCEL各种功能实现

来源:互联网 发布:vb getdc 编辑:程序博客网 时间:2024/05/21 10:01
 

VC++在MFC下调用EXCEL各种功能实现  

2010-12-13 16:59:09|  分类:默认分类 |  标签:|字号 订阅

一直以来就对EXCEL的各种功能很崇拜,后来经常使用VC,由于工作的需要,经常会遇到将文本文件中的庞大数据提取到Excel中运算处理。这个工作量可谓是劳民伤财,但是又不可不做,于是使用最简单的数据流(fscanf(), fprintf()之类)书写文本格式的Excel文件,其弱智程度我就不说了。。。
但是随着数据量的增大,这种方法每次都要处理不兼容问题,十分繁琐。于是探索如何按照Excel的数据格式写文件,解决兼容性问题。查询了各种资料,MSDN也看了很多,最后终于成功了。哈哈!!!
好吧,现在就来说说这个鬼东西如何操作。首先,我对VC++高深的架构,接口,数据类型了解的并不多,这与绝大多数人是相同的。随意很多东西都是只管拿来用,只要不出问题,就不去想他。闲话少说,现在就开始。

1。首先按照常规的方式建立对话框的MFC应用程序,然后添加类,也就是我们需要进行数据处理的类。由于只涉及到字符转换和数字提取,我建的是常规类,没有基类。

2。既然要使用EXCEL的各种功能,那么就必须包含EXCEL的类文件(excel.h, excel.cpp)。晚上有大把教程告诉我们如何从excel的OBJ文件或者EXE文件提取源文件。这里不再叙述,反正我很早以前提取过office 2003的,就一直在用。

3。如果我们看Excel的头文件(excel.h)会发现里面有大量的数据类型是我们不常使用的。因此如果要直接套用这些函数,就必须做数据类型转换,我这里使用了com库的接口,至于原理什么的不清楚。

4。首先我们需要特别说明一个数据类型,这一个数据类型在Excel操作中反复用到,就是 VARIANT 类型。这是一个结构体,里面包含了大量的数据类型,其定义如下:
struct tagVARIANT {
    union {
        struct __tagVARIANT {
            VARTYPE vt;
            WORD    wReserved1;
            WORD    wReserved2;
            WORD    wReserved3;
            union {
                ULONGLONG     ullVal;       /* VT_UI8               */
                LONGLONG       llVal;         /* VT_I8                */
                LONG                 lVal;          /* VT_I4                */
                BYTE                  bVal;        /* VT_UI1               */
                SHORT               iVal;         /* VT_I2                */
                FLOAT         fltVal;       /* VT_R4                */
                DOUBLE        dblVal;       /* VT_R8                */
                VARIANT_BOOL  boolVal;      /* VT_BOOL              */
                _VARIANT_BOOL bool;         /* (obsolete)           */
                SCODE         scode;        /* VT_ERROR             */
                CY            cyVal;        /* VT_CY                */
                DATE          date;         /* VT_DATE              */
                BSTR          bstrVal;      /* VT_BSTR              */
                IUnknown *    punkVal;      /* VT_UNKNOWN           */
                IDispatch *   pdispVal;     /* VT_DISPATCH          */
                SAFEARRAY *   parray;       /* VT_ARRAY             */
                BYTE *        pbVal;        /* VT_BYREF|VT_UI1      */
                SHORT *       piVal;        /* VT_BYREF|VT_I2       */
                LONG *        plVal;        /* VT_BYREF|VT_I4       */
                LONGLONG *    pllVal;       /* VT_BYREF|VT_I8       */
                FLOAT *       pfltVal;      /* VT_BYREF|VT_R4       */
                DOUBLE *      pdblVal;      /* VT_BYREF|VT_R8       */
                VARIANT_BOOL *pboolVal;     /* VT_BYREF|VT_BOOL     */
                _VARIANT_BOOL *pbool;       /* (obsolete)           */
                SCODE *       pscode;       /* VT_BYREF|VT_ERROR    */
                CY *          pcyVal;       /* VT_BYREF|VT_CY       */
                DATE *        pdate;        /* VT_BYREF|VT_DATE     */
                BSTR *        pbstrVal;     /* VT_BYREF|VT_BSTR     */
                IUnknown **   ppunkVal;     /* VT_BYREF|VT_UNKNOWN  */
                IDispatch **  ppdispVal;    /* VT_BYREF|VT_DISPATCH */
                SAFEARRAY **  pparray;      /* VT_BYREF|VT_ARRAY    */
                VARIANT *     pvarVal;      /* VT_BYREF|VT_VARIANT  */
                PVOID         byref;        /* Generic ByRef        */
                CHAR          cVal;         /* VT_I1                */
                USHORT        uiVal;        /* VT_UI2               */
                ULONG         ulVal;        /* VT_UI4               */
                INT           intVal;       /* VT_INT               */
                UINT          uintVal;      /* VT_UINT              */
                DECIMAL *     pdecVal;      /* VT_BYREF|VT_DECIMAL  */
                CHAR *        pcVal;        /* VT_BYREF|VT_I1       */
                USHORT *      puiVal;       /* VT_BYREF|VT_UI2      */
                ULONG *       pulVal;       /* VT_BYREF|VT_UI4      */
                ULONGLONG *   pullVal;      /* VT_BYREF|VT_UI8      */
                INT *         pintVal;      /* VT_BYREF|VT_INT      */
                UINT *        puintVal;     /* VT_BYREF|VT_UINT     */
                struct __tagBRECORD {
                    PVOID         pvRecord;
                    IRecordInfo * pRecInfo;
                } __VARIANT_NAME_4;         /* VT_RECORD            */
            } __VARIANT_NAME_3;
        } __VARIANT_NAME_2;

        DECIMAL decVal;
    } __VARIANT_NAME_1;
};
VARIANT数据结构包含两个域(如果不考虑保留的域)。vt域描述了第二个域的数据类型。为了使多种类型能够在第二个域中出现,我们定义了一个联合结构。所以,第二个域的名称随着vt域中输入值的不同而改变。用于指定vt域值情况的常量在联合的定义中以每一行的注释形式给出。
使用VARIANT和VARIANTARG数据结构要分两步完全。举一个例子,让我们考虑如下代码:

long lValue = 999;
VARIANT vParam;
vParam.vt = VT_I4;
vParam.lVal = lValue;
在第一行中指定数据类型。常量VT_I4表明在第二个域中将出现一个long型的数据。根据类型VARIANT的定义,可以得知,当一个long型数据存入VARIANT类型时,其第二个域使用的名称是lVal。

5。了解了VARIANT数据类型,我们还需要了解一个函数,我实在微软的官网查到这个函数的,网上讲解的不是很全面,我大概说下,基本上是照猫画虎,原理什么的都不懂的。这个函数就是AutoWrap()函数。可能很多人都在网上查到过这个函数,但是其工作原理不是很清楚,我觉得没必要全懂,只要理解两句就足够了。
HRESULT AutoWrap(int autoType, VARIANT *pvResult, IDispatch *pDisp, LPOLESTR ptName, int cArgs...)
{
    // Begin variable-argument list...
    va_list marker;
    va_start(marker, cArgs);

    if(!pDisp) 
{
        MessageBox(NULL, _T("NULL IDispatch passed to AutoWrap()"), _T("Error"), 0x10010);
        _exit(0);
    }

    // Variables used...
    DISPPARAMS dp = { NULL, NULL, 0, 0 };
    DISPID dispidNamed = DISPID_PROPERTYPUT;
    DISPID dispID;
    HRESULT hr;
    char buf[200];
    char szName[200];

    // Convert down to ANSI
    WideCharToMultiByte(CP_ACP, 0, ptName, -1, szName, 256, NULL, NULL);

    // Get DISPID for name passed...
    hr = pDisp->GetIDsOfNames(IID_NULL, &ptName, 1, LOCALE_USER_DEFAULT, &dispID);
    if(FAILED(hr)) 
{
        sprintf(buf, "IDispatch::GetIDsOfNames(\"%s\") failed w/err 0x%08lx", szName, hr);
        MessageBox(NULL, buf, L"AutoWrap()", 0x10010);
        _exit(0);
        return hr;
    }

    // Allocate memory for arguments...
    VARIANT *pArgs = new VARIANT[cArgs+1];
    // Extract arguments...
    for(int i=0; i<cArgs; i++) 
{
        pArgs[i] = va_arg(marker, VARIANT);
    }

    // Build DISPPARAMS
    dp.cArgs = cArgs;
    dp.rgvarg = pArgs;

    // Handle special-case for property-puts!
    if(autoType & DISPATCH_PROPERTYPUT) 
{
        dp.cNamedArgs = 1;
        dp.rgdispidNamedArgs = &dispidNamed;
    }

    // Make the call!
    hr = pDisp->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT, autoType, &dp, pvResult, NULL, NULL);
    if(FAILED(hr)) 
{
        sprintf(buf, "IDispatch::Invoke(\"%s\"=%08lx) failed w/err 0x%08lx", szName, dispID, hr);
        MessageBox(NULL, buf, "AutoWrap()", 0x10010);
        _exit(0);
        return hr;
    }
    // End variable-argument section...
    va_end(marker);

    delete [] pArgs;

    return hr;
}
其实这个函数的作用,就是将特定字符串转换成Excel命令,然后调用Invoke函数对相应数据进行处理。详细说来:
(1)。int autoType:这里只能有4个值,表示Invoke如何处理相关数据。在OLEAUTO.h文件中定义如下:
/* Flags for IDispatch::Invoke */
#define DISPATCH_METHOD         0x1
#define DISPATCH_PROPERTYGET    0x2
#define DISPATCH_PROPERTYPUT    0x4
#define DISPATCH_PROPERTYPUTREF 0x8
这里面我用了前面三个,主要的是中间的两个。
(2)。VARIANT *pvResult:Invoke处理完数据后的返回指针,指向处理结果,后面我们看到就是子函数的返回值
(3)。 IDispatch *pDisp:一个指针,用于调用DISPID方法,其实就是只调用个两个方法,即在特定层次下,将特定字符串转换成命令值,然后执行命令
(4)。LPOLESTR ptName:就是一直在说的特定字符串
(5)。int cArgs...:命令参数个数,由于是模板函数,所以后面可跟更多的参数。

好的,我们把参数都解释了一下,那么函数中有两个特殊语句我们再说一下:
(1)。hr = pDisp->GetIDsOfNames(IID_NULL, &ptName, 1, LOCALE_USER_DEFAULT, &dispID)
这个函数在微软官网上面有,但是我相信很多人没看懂。如果我们把后面的一个语句放到一块,就比较轻松了:
hr = pDisp->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT, autoType, &dp, pvResult, NULL, NULL);
我们可以打开我们的工程中的excel.cpp文件,随便找一个函数:
LPDISPATCH CalloutFormat::GetParent()
{
LPDISPATCH result;
InvokeHelper(0x1, DISPATCH_PROPERTYGET, VT_DISPATCH, (void*)&result, NULL);
return result;
}
这里面的InvokeHelper()可以看做与Invoke()相同。所以第一个参数是一个十六进制的数,每个函数这个十六进制的数都不同,所以简单来说,GetIDsOfNames函数就是将我们的特殊字符串转换成命令值。我们后面可知,这个特殊字符串实际上只能是Excel相关的函数名或者部分函数名(这个比较难理解,可以先放下,后面会举例)。
所以GetIDsOfNames最后只得到了一个int类型的值dispID,用以Invoke()函数的执行。
(2)。hr = pDisp->Invoke(disIpD, IID_NULL, LOCALE_SYSTEM_DEFAULT, autoType, &dp, pvResult, NULL, NULL);
这个函数就解释一句话:根据定义的运行类型(autotype),特定的参数(dp),运行特定的命令值(disIpD),得到运行结果(pvResult)和运行状态(hr )。如果hr不等于0,就说明运行失败。
其实这个语句跟函数的意思是相同的,只是方式改变了而已。

所以这个函数:
HRESULT AutoWrap(int autoType, VARIANT *pvResult, IDispatch *pDisp, LPOLESTR ptName, int cArgs...)
的意思就是,我们可以直接通过直接写一个函数名称(或者部分函数名称),添加相关参数(实参)后,就可以实现通常语法的函数功能,比如如下程序段:
例 1:
//select LineNum x RowNum range
IDispatch *pXlRange;
{
VARIANT parm;
parm.vt = VT_BSTR;
parm.bstrVal = ::SysAllocString(bstr_Range_Str);

VARIANT result;
VariantInit(&result);
AutoWrap(DISPATCH_PROPERTYGET, &result, pXlSheet, L"Range", 1, parm);
VariantClear(&parm);
pXlRange = result.pdispVal;
}
这个语句段是调用pXlSheet.GetRange(Range)这个函数里面的Invokehelper(),实际我们可以理解为就是在调用pXlSheet.GetRange(Range)函数。
可能有人会问:里面的字符串是"Range",为什么函数本体是GetRange()哪?
我们看第一个参数:DISPATCH_PROPERTYGET,这表示我们需要Get相关的命令值(输出),所以就是GetRange()了
如果是DISPATCH_PROPERTYPUT, 就表示此时命令值作为输入要去改变部分参数,所以就是SetRange()了
如果是DISPATCH_METHOD,则在写字符串时,需要写函数的完整名称。

我们只要一个完整的函数包括形参和返回值,那在AutoWrap()中实参怎么传递给形参哪?
这个就是int cArgs...的妙处,cArgs是一个int类型,表示需要传递的实参的数目,而cArgs后面的参数就都是要传递给函数的实参了。
我们可以看看Excel.h文件,里面的函数传递的参数各不相同,有的没有参数,有的有十几个参数。那我们要使用一个函数解决这所有的问题,就要使用如此的函数模板了。.如上面的例1。
特别需要说明的一点:如果你需要传递多个参数,参数的顺序是反向的:SetItem(parm1, parm2, parm3, parm4)。则在AutoWrap(。。。。。。,L"Item", 4, parm4, parm3, parm2, parm1)。

6。如果以上内容都理解了(我说的可能有不对的地方,不过你们看了下面的例子应该就会明白),我们就开始正式些程序了:
(1)在我们刚才建好的工程中添加文件:excel.h,excel.cpp
(2)在我们新建的那个类DatalogConvertor的cpp文件中添加:
#include <ole2.h>       //需要调用OLE方法
#include <comutil.h>   //需要调用com接口

#pragma once
#pragma comment(lib,   "comsupp.lib ")  //调用Com库

添加 HRESULT CDatalogConvertor::AutoWrap(int autoType, VARIANT *pvResult, IDispatch *pDisp, LPOLESTR ptName, int cArgs...)函数,就把上面的代码黏贴进去即可。

(3)在你的excel操作函数中开始启动Excel进程,Excel采用层次化编程,如果我们要在Excel的一个sheet上写数据,就要先开启进程,建立工作薄,在工作薄中添加一个工作页,激活此工作页(Sheet),之后才可以进行写入操作。
//initial COM lib
CoInitialize(NULL);

CLSID clsid;
HRESULT hr = CLSIDFromProgID(L"Excel.Application", &clsid);

if(FAILED(hr)) 
{
::MessageBox(NULL, "CLSIDFromProgID() function error\nEXCEL Not Be Installed!", "error", 0x10010);
//::MessageBox(NULL, "CLSIDFromProgID() function error!", "error", 0x10010);
return -1;
}

// creat instance
IDispatch *pXlApp;
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void **)&pXlApp);
if(FAILED(hr)) 
{
::MessageBox(NULL, "Pls check whether setuped EXCEL!", "error", 0x10010);
return -2;
}
// Application.Visible is ture
VARIANT IsVisible;
IsVisible.vt = VT_I4;
IsVisible.lVal = 0; //0=not visible, 1=visible
AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlApp, L"Visible", 1, IsVisible);

//get WorkBooks
IDispatch *pXlBooks;
{
VARIANT result;
VariantInit(&result);
AutoWrap(DISPATCH_PROPERTYGET, &result, pXlApp, L"Workbooks", 0);
pXlBooks = result.pdispVal;
}

//create new Workbook using Workbook.Add() method
IDispatch *pXlBook;
{
VARIANT result;
VariantInit(&result);
AutoWrap(DISPATCH_PROPERTYGET, &result, pXlBooks, L"Add", 0);
pXlBook = result.pdispVal;
}

//get Worksheet object from Application.ActiveSheet attribute
IDispatch *pXlSheet;
{
VARIANT result;
VariantInit(&result);
AutoWrap(DISPATCH_PROPERTYGET, &result, pXlApp, L"ActiveSheet", 0);
pXlSheet = result.pdispVal;
}
到这里我们顺利启动了Excel并且创建了工作薄,得到了当前的工作页。我们要写数据进去,就有两种方法:一个是一个单元一个单元的写(Cell),还有一个就是选定一个范围(Range),然后一个特定格式的数组进行添加。我这里面选取的是Range。
//fill excel file function
int CDatalogConvertor::FillExcel(IDispatch *pXlSheet, int LineStart, int LineNum, int RowStart, int RowNum, CString Data[], int flag)
{
int i, j;
int data_flag;

//***create a LineNum x RowNum arrary to fill excel format***//
VARIANT arr;
WCHAR szTmp[128];
arr.vt = VT_ARRAY | VT_VARIANT;
SAFEARRAYBOUND sab[2];

sab[0].lLbound = 1; sab[0].cElements = LineNum;
sab[1].lLbound = 1; sab[1].cElements = RowNum;
arr.parray = SafeArrayCreate(VT_VARIANT, 2, sab);

//***Convert string to BSTR and fill the data into the array***//
BSTR bstrData[128][64]={0};

for(i=0;i<LineNum;i++)
{
for(j=0;j<RowNum;j++)
{
//Convert string to BSTR
bstrData[i][j]=_com_util::ConvertStringToBSTR(Data[i+j]);
VARIANT tmp;
tmp.vt = VT_BSTR;
wsprintfW(szTmp,bstrData[i][j],i,j);
tmp.bstrVal = SysAllocString(szTmp);
//fill the data into the array
long indices[]={i+1,j+1};
SafeArrayPutElement(arr.parray,indices,(void *)&tmp);
}
}

//Math Line Number and Row Number for excel range and array
char Row_Start = (char) (RowStart+64); //convert 1 to A
char Row_Stop  = Row_Start+RowNum-1;
CString Range_Str, LineStart_Str, LineStop_Str;
BSTR bstr_Range_Str;

LineStart_Str.Format("%d", LineStart);
LineStop_Str.Format("%d", LineStart+LineNum-1);
Range_Str = _T(Row_Start)+LineStart_Str+_T(":")+_T(Row_Stop)+LineStop_Str;
bstr_Range_Str = _com_util::ConvertStringToBSTR(Range_Str);

//select LineNum x RowNum range
IDispatch *pXlRange;
{
VARIANT parm;
parm.vt = VT_BSTR;
parm.bstrVal = ::SysAllocString(bstr_Range_Str);

VARIANT result;
VariantInit(&result);
AutoWrap(DISPATCH_PROPERTYGET, &result, pXlSheet, L"Range", 1, parm);
VariantClear(&parm);
pXlRange = result.pdispVal;
}

//fill the Range by our array
AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlRange, L"Value", 1, arr);
pXlRange->Release();

return 0;
}
这个函数里面最后的AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlRange, L"Value", 1, arr);
就是将数组填写进相关的Range中。

(4)写完了数据后就要保存文件退出Excel进程:
  //***************************************************** //save Excel file and quit excel.exe //***************************************************** //save excel file through Worksheet.SaveAs(), ignore all parameter expect filemname. VARIANT filename; filename.vt = VT_BSTR; filename.bstrVal = SysAllocString(_com_util::ConvertStringToBSTR(FileSaveName)); AutoWrap(DISPATCH_METHOD, NULL, pXlSheet, L"SaveAs", 1, filename); SysFreeString(filename.bstrVal); // exit EXCEL app through Application.Quit() AutoWrap(DISPATCH_METHOD, NULL, pXlApp, L"Quit", 0); //release all parameter //pXlRange->Release(); pXlSheet->Release(); pXlBook->Release(); pXlBooks->Release(); pXlApp->Release(); //VariantClear(&arr); //********************************************************* //close files //********************************************************* //close COM lib CoUninitialize();

(5)如果我们除了写数据意外还想做点其他的操作,如改变字体格式,颜色,填充颜色,设置宽度,甚至更高阶的功能,不用着急,AutoWrap()的功能弄清楚之后就很容易:
CString cstr_Line, cstr_Range;
BSTR    bstr_Range;
cstr_Line.Format("%d",data_line);
cstr_Range = _T("A")+cstr_Line+_T(":")+_T("A")+cstr_Line;
bstr_Range = _com_util::ConvertStringToBSTR(cstr_Range);
//select LineNum x RowNum range 得到Range
IDispatch *pXlRange;
{
VARIANT parm;
parm.vt = VT_BSTR;
parm.bstrVal = ::SysAllocString(bstr_Range);

VARIANT result;
VariantInit(&result);
AutoWrap(DISPATCH_PROPERTYGET, &result, pXlSheet, L"Range", 1, parm);
VariantClear(&parm);
pXlRange = result.pdispVal;
}
//Set Column Width object form Range.SetColumnWidth() attribute
//设置此部分Range的列宽度
{
VARIANT parm;
parm.vt = VT_I4;
parm.lVal = 40.0;
AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlRange, L"ColumnWidth", 1, parm);
}
//get Font object from Range.GetFont() attribute
//得到相应Range内默认的字体属性
IDispatch *pXlFonts;
{
VARIANT result;
VariantInit(&result);
AutoWrap(DISPATCH_PROPERTYGET, &result, pXlRange, L"Font", 0);
pXlFonts = result.pdispVal;
}
//Set Font object from Range.SetColor() attribute
//设定字体颜色
IDispatch *pXlFont;
{
VARIANT result;
VariantInit(&result);
VARIANT parm;
parm.vt = VT_I4;
parm.lVal = RGB(255,0,0); //red color
AutoWrap(DISPATCH_PROPERTYPUT, &result, pXlFonts, L"Color", 1,parm);
pXlFont = result.pdispVal;
}
//get Interior object from Range.GetInterior() attribute
//得到相应Range内的框体默认属性
IDispatch *pXlInterior;
{
VARIANT result;
VariantInit(&result);
AutoWrap(DISPATCH_PROPERTYGET, &result, pXlRange, L"Interior", 0);
pXlInterior = result.pdispVal;
}
//Set Back Color object from Interior.SetColor() attribute
//设定框体背景颜色
IDispatch *pXlBackColor;
{
VARIANT result;
VariantInit(&result);
VARIANT parm;
parm.vt = VT_I4;
parm.lVal = RGB(140,227,190); //blue back color
AutoWrap(DISPATCH_PROPERTYPUT, &result, pXlInterior, L"Color", 1,parm);
pXlBackColor = result.pdispVal;
}
所以只要想添加什么功能,在Excel.h文件中找到相关的函数,根据函数名和参数类型,定制AutoWrap()函数,代码会变得很清晰整洁。同时据微软官网说,这也会是代码执行效率提高很多(不知是真是假。。)
原创粉丝点击