Link to home
Start Free TrialLog in
Avatar of re
re

asked on

OLE and MS Project

I try to write a program in Visual C++ 5.0 using MFC which can remotely control MS Project using OLE.

I found out that I have to write an OLE automation client.

I would like to know, how to find out the names of objects or functions MS Project offers to the outside??

If I have to do this via a ".tbl" file, can someone tell me the name of this file which MS Project 95 version 4.1a offers??

Thanks.
ASKER CERTIFIED SOLUTION
Avatar of MFCGuy
MFCGuy

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of MFCGuy
MFCGuy

In response to your acceptance of my answer, where you asked how to use the file:

After you have generated the classes using "New Class -> From TypeLib" in ClassWizard, you can do something like this, where I am setting the start date value of the first task
(I named all the classes from the TypeLib ProjectXXX, where XXX
is a class generated from the TypeLib):

ProjectApplication* dispatch = new ProjectApplication;

// create the object that we'll drive through OLE automation
COleException e;
CLSID clsid;
if (CLSIDFromProgID(OLESTR("MSProject.Application"), &clsid) != NOERROR)
{
      AfxMessageBox("Could not create MSProject object");
      return FALSE;
}

// try to get the active object before creating a new one
LPUNKNOWN lpUnk;
LPDISPATCH lpDispatch;
if (GetActiveObject(clsid, NULL, &lpUnk) == NOERROR)
{
      HRESULT hr = lpUnk->QueryInterface(IID_IDispatch,
            (LPVOID*)&lpDispatch);
      lpUnk->Release();
      if (hr == NOERROR)
            dispatch->AttachDispatch(lpDispatch, TRUE);
}


// if not dispatch ptr attached yet, need to create one
if (dispatch->m_lpDispatch == NULL && 
      !dispatch->CreateDispatch(clsid, &e))
{
      AfxMessageBox("Could not create Project Object");
      return FALSE;
}

char title[128];
BOOL found = FALSE;
CWnd * pWnd = AfxGetMainWnd();
while (pWnd != NULL)
{
      pWnd->GetWindowText(title,sizeof(title));
      if((CString(title).Find("Microsoft Project")) != -1)
      {
            found = TRUE;
            break;
      }
      pWnd = pWnd->GetWindow(GW_HWNDNEXT);
}
if(!found)//try another method if it is a brand-new window
{
      pWnd = CWnd::FindWindow(NULL,"Microsoft Project");
      if(pWnd == NULL) return FALSE;
}

//Bring the window to the front
if(pWnd || found)
{
      pWnd->ShowWindow(SW_SHOWMAXIMIZED);
      pWnd->UpdateWindow();
      pWnd->BringWindowToTop();

      //Open the file
      dispatch->FileOpen(      COleVariant(fileName) );

      //Fill out template

      ProjectProject project;
      ProjectTasks tasks;

      tmpDisp = dispatch->GetActiveProject();
      project.AttachDispatch(tmpDisp, TRUE);

      tmpDisp = project.GetTasks();
      tasks.AttachDispatch(tmpDisp, TRUE);

      int count = ( tasks.Count() ).iVal;//get # of tasks in plan
      if(!count) return FALSE;

      //Set Project Start Date
      project.SetProjectStart(COleVariant(startDate));

      //Reactivate in case of template errors
      pWnd->ShowWindow(SW_SHOWMAXIMIZED);
      pWnd->UpdateWindow();
      pWnd->BringWindowToTop();
}
else
{
      AfxMessageBox("Project could not be started!");
      return FALSE;
}


I hope that gives you more information as well as giving you a better impression of Experts Exchange.  I would have been glad to provide this information up front if I had realized you needed it.  I will try and be more proactive in my answers in the future.

Regards,
MFCGuy
Avatar of re

ASKER

This comment seems to be very useful.

Thanks a lot, Ralf
Avatar of re

ASKER

Hi MFCGuy,

I couldn't find out your email-address, so I write a message on this way.

I have a problem belonging to your source code in your answer. In the first line you declare a variable of type "ProjectApplication" but VC++ 5.0 doesn't know that type, I can't find the word in the online help either. So can you please tell me where to finnd this type (in a header file or class library or...)

Thanks, re
When you generate the classes by doing NewClass->FromTypeLib
in ClassWizard, ClassWizard will generate a class called "Application" that represents the MS Project application VBA object.  I changed the name to "ProjectApplication" during that
import process.

I actually put "Project" in front of all the classes it generated to simplify the listing in the ClassView and to not conflict with the Application class generated by some other TypeLibs I am using.

Hope I helped.  Please respond if the above did not make sense.

MFCGuy
Avatar of re

ASKER

I was a little bit confused about your last comment. I searched for the "New class" in ClassWizard. The entry is "AddClass". I think that's what you meant. I think now I can try to type in your code and find out if it works. I let you know about the results.

Thanks so far, re
Yes "Add Class" is correct.  I was going from memory.

Sorry.  Let me know what happens.

MFCGuy
Avatar of re

ASKER

I'm sorry to ask again something but I found some "errors" in your code.

Your code works very fine until Project starts and the project window comes to the front.

There are three places where the compiler finds something:
1) The variable "fileName" is undeclared. I think it can be declared as type "char[]" and then directly assigned with a value.

2) The call to function "FileOpen" is wrong. You use only one parameter but the header file shows to specify many more. Can you please correct this call and tell me how to find out which type a parameter must be. The header file shows all parameters of type VARIANT, which is only used to transport the value, I think.

3) The variable "startDate" isn't declared. Can you please tell me, which type to use for this variable.

Sorry, but it would be useful for me if you could change the code, so that it directly works and post it as a new comment. Please don't forget the explanations. Sorry for this much work without extra bonus points.

I hope this was all about your code and thanks for your help so far, re
1.fileName is the File name you want to open, e.g.
CString fileName = "C:\\MyPlan.prj"
or something like that.

3.startDate is a date you want to set - this was just an example
so use COleDateTime::GetCurrentTime() if you want to use today's date.
COleDateTime startDate = COleDateTime::GetCurrentTime();
startDate.SetDate(startDate.GetYear(),startDate.GetMonth(),startDate.GetDay());  //used to zero out the time

2. Wrong?  How dare you!  :-)
I forgot about that...  I overrode the FileOpen function so I didn't have to use all those parameters.  Here is the code for the class generated from ClassWizard:
in the .h file
--------------
void FileOpen(const VARIANT& Name);
Put it under the normal declaration, e.g.
void FileOpen(const VARIANT& Name, const VARIANT& ReadOnly, const VARIANT& Merge, const VARIANT& TaskInformation, const VARIANT& Table, const VARIANT& Sheet, const VARIANT& NoAuto);

in the .cpp file
----------------
void ProjectApplication::FileOpen(const VARIANT& Name)
{
      static BYTE parms[] =
            VTS_VARIANT;
      InvokeHelper(0x66, DISPATCH_METHOD, VT_EMPTY, NULL, parms, &Name);
}
Put it under the standard declaration, e.g.:
void ProjectApplication::FileOpen(const VARIANT& Name, const VARIANT& ReadOnly, const VARIANT& Merge, const VARIANT& TaskInformation, const VARIANT& Table, const VARIANT& Sheet, const VARIANT& NoAuto)
{
      static BYTE parms[] =
            VTS_VARIANT VTS_VARIANT VTS_VARIANT VTS_VARIANT VTS_VARIANT VTS_VARIANT VTS_VARIANT;
      InvokeHelper(0x66, DISPATCH_METHOD, VT_EMPTY, NULL, parms,
             &Name, &ReadOnly, &Merge, &TaskInformation, &Table, &Sheet, &NoAuto);
}

In the .cpp File, remember to replace the class I have (ProjectApplication) with the class name you generated (probably Application).

Remember, my example sets the start date of the first task, so make sure the file you are opening has at least one task (or remove that code).

Regards,
MFCGuy

P.S. Don't worry about the points, I only need your thanks (which I've already gotten).

Avatar of re

ASKER

I forgot something in my last comment.

The variable "tmpDisp" was also not declared. I think it was right to use the type "ProjectApplication" for this variable (I renamed the class "Application" also to "ProjectApplication").

If this was right, I don't understand an exception which occurs in my program. The exception is described as an access vialation. This happens when the program tries to set the project start. My project file includes two tasks and is opened properly. Maybe you have some ideas about that.

Thanks, re
LPDISPATCH tmpDisp;

That is probably yuor access exception.
LPDISPATCH tmpDisp;

That is probably your access exception.
Avatar of re

ASKER

Thank you that was the correct data type and now it works. I don't know why my first task doesn't move, if the current time is used for project start and it was set before to another date but that doesn't matter now. Maybe I'm not familiar enough with MS Project.

There is still a last question:
How can I find out which datatype to use for a parameter because the .h file with the class definition from the server application show all the parameters with "VARIANT" as type. It would be nice if you could also answer this last question.

Thanks, re
All function parameters and all OLE calls are explained in the
MSProject on-line VBA help. (VBA_pj.hlp)

In my experience, which is very limited with MS Project OLE calls, it is best to override any function (like I did with FileOpen) that has optional parameters that are useless under
normal circumstances.  Maybe someday ClassWizard will generate all the overrides that the MS Products provide using VBA directly...

Good Luck!