Click here to Skip to main content
15,906,467 members
Please Sign up or sign in to vote.
1.50/5 (4 votes)
See more:
Hello everyone,this is my first post here.
I would like to start by saying thanks to everyone who takes some time to view this thread and try to help.

The problem i have is following:

I have read following Microsoft article(http://support.microsoft.com/kb/216686[^]):

"How to automate Excel from C++ without using MFC or #import"

and code presented there suites my needs.

However,the range specified there is constant (L"A1:O15") since the array is 15x15,
but my array is set through variables( int row,column;) so i need to calculate range dynamically.

Helper function in the article (AutoWrap()) takes constant string(L"A1:O15") and converts it to range.

My question is following:

How do I create string to pass to helper function (AutoWrap()) so I can get range from A1 to xy,where x is last column("Z" or "AA" or "AAA" ) and y is number of rows;

I already have stored number of rows in row variable,but I can't convert the number of columns to appropriate string.

To clarify,if my array is 10x5 how do i pass string L"A1:E10" to helper function AutoWrap() ?

I use MS Visual C++ Express addition and work in pure Win32 API.
Posted
Updated 3-Jun-12 20:29pm
v4
Comments
MyOldAccount 28-May-12 7:26am    
nv3 thanks again for the code,but it crashes at following line:

_stprintf (buffer, _T("A1::%s%d"), colName, cntRows);

*************************************************************************

losmac,thank you for reference,that was my thought too,but I don't know how to do it via IDispatch since all this is new to me.

******************************************************************************

I am not giving up,and will keep trying both suggestions.I will post my results soon.
MyOldAccount 28-May-12 9:51am    
******************************************************************************

FIRST BREAKTHROUGH

******************************************************************************

Algorythm provided by nv3 fills colName array in reverse order ("BA" instead of "AB"),but swprintf part where colName variable should be concatenated to L"A1:" doesn't work-on print i just get "A".

At this point I am preparing to go home since my work finishes.

Until then i wish best to all!

If you have the row and column numbers already in variables, the task of creating the range string should be easy:

int cntRows = 5;
int cntCols = 10;

wchar_t buffer [30];
swprintf (buffer, L"A1::%c%d", 'A'+cntCols-1, cntRows);


This works as long as your cntCols is lower or equal to 26. To generate column designators like AA, AB, AC etc. it takes just a little more work:

wchar_t colName[3];
if (cntCols <= 26)
    swprintf (colName, L"%c", 'A' + cntCols - 1);
else if (cntCols <= 26*26)
    swprintf (colName, L"%c%c", 'A' + cntCols%26 - 1, 'A' + cntCols/26 - 1);
else
    ; // ... error processing	

wchar_t buffer [30];
swprintf (buffer, L"A1::%s%d", colName, cntRows);


Hope that helps.

EDIT: Replace char with wchar_t in the second code snipped.
EDIT 2: Replaced _stprintf by swprintf
 
Share this answer
 
v3
Comments
MyOldAccount 28-May-12 6:28am    
SysAllocString() must take const olechar*,and buffer is char* variable.How do I make conversion so I can test your solution?

Thanks again.
nv3 28-May-12 8:00am    
An OLECHAR is a wchar_t, so you can pass buffer directly to SysAllocString.
MyOldAccount 28-May-12 8:29am    
It is _stprintf that makes the issue...

_stprintf (buffer, L"A1::%s%d", colName, cntRows); gives me following error:

error C2664: 'sprintf' : cannot convert parameter 1 from 'wchar_t *' to 'char *'

I am a bit confused...
nv3 28-May-12 9:05am    
Sorry, I should have replaced _stprintf by swprintf. The trouble you had was because you are probably not compiling for Unicode, but for multi-byte char strings (see your project settings). With swprintf it is clear to the compiler that you want to use the Unicode variant of the function.
MyOldAccount 28-May-12 9:21am    
Yes that is true.It works now,but seems there is some problem with the algorythm of yours when I run it.Perhaps the problem is in my coding so I will try to fix it,and post about it later.Thank you anyway!
To calculate the size of range(array), use Cells for Range() object.

VBA:
VB
Dim rng As Range
Dim iRowCount As Integer, iColCount As Integer

Set rng = ThisWorkbook.Worksheets(1).Range("A1:C15")
'get columns count
iColCount = rng.Cells.Columns.Count
'get rows count
iRowCount = rng.Cells.Rows.Count

'display message
MsgBox "Columns count: " & iColCount & vbCr & "Rows count: " & iRowCount, vbInformation, rng.Address

Set rng = Nothing


More at:
How to reference Cells and Ranges[^]
The above link is related to Excel 2003 VBA reference, but explain how to achieve what you want.
 
Share this answer
 
Comments
MyOldAccount 28-May-12 6:30am    
Thank you for advice,but I don't know VB so it is hard for me to adapt at this moment.I will try this after I get home(i am at work now).
Again,thank you very much.
Here's the code from the example at Microsoft put together into a single file that will compile with gcc 4.2.something.

A few things to note:
  1. I've added variables rows and cols - these control the size of the range affected in excel.
  2. I had to add some GUID definitions to the file since not using MS compiler
  3. You need to link the ole32 and oleaut32 libraries - .lib for VS, .a for gcc
  4. nv3 was spot-on saying that you can simply use the buffer for SysAllocString
  5. I've included the entire source in the interests of completeness



C++
#define UNICODE
#include <windows.h>
#include <stdio.h>
#include <ole2.h> // OLE2 Definitions


const GUID GUID_NULL = { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } };
const GUID IID_IUnknown = {0x00000000, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
const GUID IID_IDispatch = {0x00020400, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};

int cols=8, rows=16;

// AutoWrap() - Automation helper function...
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, L"NULL IDispatch passed to AutoWrap()", L"Error", 0x10010);
        _exit(0);
    }

    // Variables used...
    DISPPARAMS dp = { NULL, NULL, 0, 0 };
    DISPID dispidNamed = DISPID_PROPERTYPUT;
    DISPID dispID;
    HRESULT hr;
    wchar_t 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)) {
        wsprintf(buf, L"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)) {
        wsprintf(buf, L"IDispatch::Invoke(\"%s\"=%08lx) failed w/err 0x%08lx", szName, dispID, hr);
        MessageBox(NULL, buf, L"AutoWrap()", 0x10010);
        _exit(0);
        return hr;
    }
    // End variable-argument section...
    va_end(marker);

    delete [] pArgs;

    return hr;
}

int main()
{
// Initialize COM for this thread...
   CoInitialize(NULL);

   // Get CLSID for our server...
   CLSID clsid;
   HRESULT hr = CLSIDFromProgID(L"Excel.Application", &clsid);

   if(FAILED(hr)) {

      MessageBox(NULL, L"CLSIDFromProgID() failed", L"Error", 0x10010);
      return -1;
   }

   // Start server and get IDispatch...
   IDispatch *pXlApp;
   hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void **)&pXlApp);
   if(FAILED(hr)) {
      MessageBox(NULL, L"Excel not registered properly", L"Error", 0x10010);
      return -2;
   }

   // Make it visible (i.e. app.visible = 1)
   {

      VARIANT x;
      x.vt = VT_I4;
      x.lVal = 1;
      AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlApp, L"Visible", 1, x);
   }

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

   // Call Workbooks.Add() to get a new workbook...
   IDispatch *pXlBook;
   {
      VARIANT result;
      VariantInit(&result);
      AutoWrap(DISPATCH_PROPERTYGET, &result, pXlBooks, L"Add", 0);
      pXlBook = result.pdispVal;
   }

   // Create a 15x15 safearray of variants...
   VARIANT arr;
   arr.vt = VT_ARRAY | VT_VARIANT;
   {
      SAFEARRAYBOUND sab[2];
      sab[0].lLbound = 1; sab[0].cElements = rows;
      sab[1].lLbound = 1; sab[1].cElements = cols;
      arr.parray = SafeArrayCreate(VT_VARIANT, 2, sab);
   }

   // Fill safearray with some values...
   for(int i=1; i<=rows; i++)
   {
      for(int j=1; j<=cols; j++)
      {
         // Create entry value for (i,j)
         VARIANT tmp;
         tmp.vt = VT_I4;
         tmp.lVal = i*j;
         // Add to safearray...
         long indices[] = {i,j};
         SafeArrayPutElement(arr.parray, indices, (void *)&tmp);
      }
   }

   // Get ActiveSheet object
   IDispatch *pXlSheet;
   {
      VARIANT result;
      VariantInit(&result);
      AutoWrap(DISPATCH_PROPERTYGET, &result, pXlApp, L"ActiveSheet", 0);
      pXlSheet = result.pdispVal;
   }

   // Get Range object for the Range A1:O15...
   IDispatch *pXlRange;
   {
      VARIANT parm;
      parm.vt = VT_BSTR;
      wchar_t buffer[32];
      wsprintf(buffer, L"%c%d:%c%d", 'A'+0, 1, 'A'+cols-1, rows);
      parm.bstrVal = SysAllocString(buffer);
      //parm.bstrVal = SysAllocString(L"A1:O15");

      VARIANT result;
      VariantInit(&result);
      AutoWrap(DISPATCH_PROPERTYGET, &result, pXlSheet, L"Range", 1, parm);
      VariantClear(&parm);

      pXlRange = result.pdispVal;
   }

   // Set range with our safearray...
   AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlRange, L"Value", 1, arr);

   // Wait for user...
   MessageBox(NULL, L"All done.", L"Notice", 0x10000);

   // Set .Saved property of workbook to TRUE so we aren't prompted
   // to save when we tell Excel to quit...
   {
      VARIANT x;
      x.vt = VT_I4;
      x.lVal = 1;
      AutoWrap(DISPATCH_PROPERTYPUT, NULL, pXlBook, L"Saved", 1, x);
   }

   // Tell Excel to quit (i.e. App.Quit)
   AutoWrap(DISPATCH_METHOD, NULL, pXlApp, L"Quit", 0);

   // Release references...
   pXlRange->Release();
   pXlSheet->Release();
   pXlBook->Release();
   pXlBooks->Release();
   pXlApp->Release();
   VariantClear(&arr);


   // Uninitialize COM for this thread...
   CoUninitialize();


    return 0;
}
 
Share this answer
 
v2
Comments
MyOldAccount 28-May-12 9:28am    
Thank you!But it seems that maximum range I can get is from 'A' to 'Z' or am I mistaking?
enhzflep 28-May-12 9:33am    
Nope, not mistaken.
Never even considered sheets wider than 26 columns.

The following two are equivalent in vba:
Cells.Range(Cells(1, 1), Cells(iSize, iSize)) = myArray
Cells.Range("A1", "J10") = myArray

Perhaps a similar approach to #1 could be used. (?)
MyOldAccount 28-May-12 9:36am    
The whole point is how to approach Cells method from IDispatch.I just don't know how since I am begginer.If I solve this,well then that should be it.
Thanks for the answer.
stib_markc 29-May-12 2:40am    
5!
I have solved it finally!!

enhzflep was right!However he didn't read the article properly.The first solution deals with base where 'A'=0,yet we need the case where 'A'=1,which is described at the bottom of the article he pointed me on (http://en.wikipedia.org/wiki/Hexavigesimal[^]) under the title: Bijective base 26.

After some slight changes,it works like a charm.

I WOULD LIKE TO THANK EVERYONE WHO VISITED THIS POST AND TRIED TO HELP.

ESPECIALLY I WOULD LIKE TO THANK MEMBERS enhzflep and nv3 FOR PROVIDING USEFULL CODE AND RESOURCES.

C++
#define UNICODE 
#include <stdio.h>
#include <stdlib.h>
#include <string.h> 
#include <string> 
#include <windows.h> 
using namespace std; 
wstring toBase26(int number) 
{
	number = abs(number);
	wstring converted = L""; 
	int iteration = 0; // the variable that makes the difference!

	// Repeatedly divide the number by 26 and convert the 
	// remainder into the appropriate letter.
	do
	{ 
		int remainder = number % 26;
		if( iteration && number<25) // this line was missing!
			remainder--;
		converted = (wchar_t)(remainder + 'A') + converted;
		number = (number - remainder) / 26; 

		iteration = 1; // and this one!
	} 
	while (number > 0); 
	return converted; 
} 

wstring makeRangeStr(int firstCol, int lastCol, int firstRow, int lastRow)
{ 
	wstring result, tmp;
	wchar_t buffer[16];
	result = toBase26(firstCol-1);
	wsprintf(buffer, L"%d", firstRow);
	result += buffer;
	result += L":"; 
	result += toBase26(lastCol-1); 
	wsprintf(buffer, L"%d", lastRow); 
	result += buffer; 
	return result; 
} 

int main() 
{ 
	int cols=29, rows=18; 
	wstring tmp = makeRangeStr(1,cols,1,rows); 
	wprintf(L"%s\n", tmp.c_str());
	return 0; 
}
 
Share this answer
 
v4
Hi again,I have changed nv3's solution a bit and it works!

Here is the changed code:

C++
wchar_t colName[3];

     if (cntCols <= 26)
	 swprintf_s (colName, L"%c", 'A' + cntCols - 1);
     else 
          if (cntCols <= 26*26)
	     swprintf_s (colName, L"%c%c", 'A' + cntCols /26 - 1, 'A' + cntCols %26 - 1);
	  else
	      // ... error processing	


wchar_t buffer[256]; 

swprintf_s (buffer, L"A1:%s%d", colName, cntRows+1); // +1 because in first row will be names of columns!

VARIANT parm;
parm.vt = VT_BSTR;
parm.bstrVal = ::SysAllocString(buffer);


But it is only good for range up to "ZZ",I want solution for any range,if possible.

I don't have concrete solution yet,but this is what i've got for now:

With following code i can get the number of letters in string e.g. "AAA" = 3 letters,"ZX" = 2 and so on, so I could allocate char array to fill it,and pass it to SysAllocString():

C++
int i=26,cntLettersInString=1;

while(cntCols - i)  
{
   cntLettersInString++;
   
   i* = 26;
}


Also the expression nv3 used for calculating letters can be used like this:

The last letter in the string should be:

C++
if( cntCols %(i/26)>26)
    'A' + cntCols %(i/26) -26 -1; // -26 since it is greater than 'Z'
    'A' + cntCols %(i/26) - 1;



The first letter can be calculated like this:

C++
'A' + cntCols /(i/26) -1;


On paper it seems ok,but I need verifycation from experienced and better members,and also I need to create algorythm for middle letter ('B' in "ABC",'V' and 'B' for "ABVA" and so on).It seems that sprintf + modification of above expressions is the key...

Again thank you all for help,nv3's solution works as described above,and if someone can help me with this I would be very greatfull.
 
Share this answer
 
v2
Comments
enhzflep 29-May-12 3:30am    
Here's a pair of helper functions you can use:


#define UNICODE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <windows.h>
using namespace std;


wstring toBase26(int number)
{
number = abs(number);
wstring converted = L"";
// Repeatedly divide the number by 26 and convert the
// remainder into the appropriate letter.
do
{
int remainder = number % 26;
converted = (wchar_t)(remainder + 'A') + converted;
number = (number - remainder) / 26;
} while (number > 0);

return converted;
}

wstring makeRangeStr(int firstCol, int lastCol, int firstRow, int lastRow)
{
wstring result, tmp;
wchar_t buffer[16];

result = toBase26(firstCol-1);
wsprintf(buffer, L"%d", firstRow);
result += buffer;

result += L":";

result += toBase26(lastCol-1);
wsprintf(buffer, L"%d", lastRow);
result += buffer;
return result;
}


int main()
{
int cols=8, rows=16;

wstring tmp = makeRangeStr(1,cols,1,rows);
wprintf(L"%s\n", tmp.c_str());

return 0;
}
MyOldAccount 29-May-12 3:46am    
Thank you,I will try it out and post reply as soon as possible!
MyOldAccount 29-May-12 4:54am    
I have tested function toBase26 on paper and I think your algorithm works,with small adjustments.

1.)

Pass number of columns,not (number of columns-1).

2.)

converted = (wchar_t)(--remainder + 'A') + converted; //first substract remainder by 1!

After these slight changes,on paper,seems that it works fine.

My program doesn't support UNICODE,so I have compiler issues with your code:)

Will post soon after I fix them,thanks for this code!
enhzflep 29-May-12 5:05am    
Pleasure. It was actually ripped straight from the java example at wikipedia: http://en.wikipedia.org/wiki/Hexavigesimal

Here's a non-unicode version of same. Please run the program and see that the range specified corresponds to a 8x16 selection, starting at E5.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <windows.h>
using namespace std;


string toBase26(int number)
{
number = abs(number);
string converted = "";
// Repeatedly divide the number by 26 and convert the
// remainder into the appropriate letter.
do
{
int remainder = number % 26;
converted = (char)(remainder + 'A') + converted;
number = (number - remainder) / 26;
} while (number > 0);

return converted;
}

string makeRangeStr(int firstCol, int lastCol, int firstRow, int lastRow)
{
string result, tmp;
char buffer[16];

result = toBase26(firstCol-1);
sprintf(buffer, "%d", firstRow);
result += buffer;

result += ":";

result += toBase26(lastCol-1);
sprintf(buffer, "%d", lastRow);
result += buffer;
return result;
}


int main()
{
int cols=8, rows=16;
int fCol=5, fRow=5;

string tmp = makeRangeStr(fCol,cols+fCol-1,fRow,rows+fRow-1);
printf("%s\n", tmp.c_str());

return 0;
}
MyOldAccount 29-May-12 5:57am    
I have made the UNICODE version above,and added int fCol=5, fRow=5; and changed call to makeRangeStr().

It prints E5:L20 so i guess it works...

Still I have issues in my program,but as soon as I solve them I will post again!

Thanks anyway.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900