首页 > 学院 > 开发设计 > 正文

MFC加载EXCEL代码

2019-11-08 02:23:38
字体:
来源:转载
供稿:网友
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 rangeIDispatch *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 libCoInitialize(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 instanceIDispatch *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 tureVARIANT IsVisible;IsVisible.vt = VT_I4;IsVisible.lVal = 0; //0=not visible, 1=visibleAutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlApp, L"Visible", 1, IsVisible);//get WorkBooksIDispatch *pXlBooks;{VARIANT result;VariantInit(&result);AutoWrap(DISPATCH_PROPERTYGET, &result, pXlApp, L"Workbooks", 0);pXlBooks = result.pdispVal;}//create new Workbook using Workbook.Add() methodIDispatch *pXlBook;{VARIANT result;VariantInit(&result);AutoWrap(DISPATCH_PROPERTYGET, &result, pXlBooks, L"Add", 0);pXlBook = result.pdispVal;}//get Worksheet object from Application.ActiveSheet attributeIDispatch *pXlSheet;{VARIANT result;VariantInit(&result);AutoWrap(DISPATCH_PROPERTYGET, &result, pXlApp, L"ActiveSheet", 0);pXlSheet = result.pdispVal;}到这里我们顺利启动了Excel并且创建了工作薄,得到了当前的工作页。我们要写数据进去,就有两种方法:一个是一个单元一个单元的写(Cell),还有一个就是选定一个范围(Range),然后一个特定格式的数组进行添加。我这里面选取的是Range。//fill excel file functionint 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 BSTRbstrData[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 arraylong indices[]={i+1,j+1};SafeArrayPutElement(arr.parray,indices,(void *)&tmp);}}//Math Line Number and Row Number for excel range and arraychar Row_Start = (char) (RowStart+64); //convert 1 to Achar 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 rangeIDispatch *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 arrayAutoWrap(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 libCoUninitialize();(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 得到RangeIDispatch *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 colorAutoWrap(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 colorAutoWrap(DISPATCH_PROPERTYPUT, &result, pXlInterior, L"Color", 1,parm);pXlBackColor = result.pdispVal;}所以只要想添加什么功能,在Excel.h文件中找到相关的函数,根据函数名和参数类型,定制AutoWrap()函数,代码会变得很清晰整洁。同时据微软官网说,这也会是代码执行效率提高很多(不知是真是假。。)
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表