Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to transfer a picture from an MFC client to an ATL ActiveX Ctrl

0.00/5 (No votes)
12 Mar 2002 1  
How to pass a metafile to an ATL server directly or using streams

Sample Image

Introduction:

Every book, and every single tutorial, teaches you how to use the basic things of COM with very good C samples. That's pretty good to begin with, but the problem comes when you want to pass from your client to your COM server/control something more than one numeric value, or one string, or... for example a Metafile, or a Bitmap... then you start having problems with the Variant type...

That was my nightmare during some days. I wanted to pass a metafile from my MFC client to my ATL ActiveX control. Here I show you how I did it (in the same process space and in different ones).

I have added some basic info about how to make both projects, if are used to it you can skip it, if you need more info about them, you can check out some basic tutorials that you can find in codeproject as well.

Create the ATL Control:

The ATL control that we are going to create is a full ActiveX control, with one interface that will allow us assign one metafile to the it ( then that metafile will be shown, in the space of the ActiveX control):

  • Go to "File --> new"
  • Select "ATL Com Appwizard"
  • Click next...
  • When the project is created, just go to the Workspace docking and select the tab "Class View"
  • Then go to the root of the control shown in that view, show the context menu and select the option "New Atl Object" 
  • From the wizard select the type of control "Full Control" ( and name the control "PicShower").
  • Now go to the ClassView tab ( in the workSpace docking), and select the interface IPicShower, press with the right button, and select the option "Add Method", then add a method called "SetDirectMeta" with the param "LPUNKNOWN Picture", and repeat the same operation to add a new method, this time called "SetMeta", with the param "LPUNKNOWN Stream".

Ok, we will add now a member variable to our brand new object, that will hold the metafile, so go to "PicShower.H", and add this variable:

// Better to use smart pointers, just forget about AddRef, Release, QInterface...

CComQIPtr<IPICTURE>  _Pict;
The "CComQIPtr" thing, it's one of the smart pointers that ATL offers to us. They make it a lot easier to handle COM objects as it makes the code look like Visual Basic (it wraps all the QueryInterface, AddRef, Release...). And the IPicture? It�s the interface to a standard component that allows you to display Metafiles, Bitmaps ( jpg, gif, tiff...). Cool isn�t it ?.

Now let's code a little. Go to the implementation of the method "SetDirectMeta" (the wizard implemented an empty skeleton for us, in the file "PicShower.cpp", the name of the method is CPicShower::SetDirectMeta(LPUNKNOWN Picture) and add this code:

/* -------------------------------------------------------------------------
      This only works, if it�s in the same space process
   ------------------------------------------------------------------------- */

STDMETHODIMP CPicShower::SetDirectMeta(LPUNKNOWN Picture)
{
   _Pict = Picture;        // Assign the picture

   FireViewChange();    // Force to redraw the ActiveX


   return S_OK;    
}

This method call will allow us to assign the metafile, but this only will work if we use the component in the same space process ( you can not share a DC, between different process spaces), so if you use this code in an EXE server, this method won't work, or if for example you paste the ActiveX in an automated Word instance and you use it from your application.

To solve this we can use this other method: go to the implementation, of the method "SetMeta", CPicShower::SetMeta(LPUNKNOWN Picture) and add this code:

/* -------------------------------------------------------------------------
      Ok, this method works in all the places ( in proccess, out of
   process, ...). The Picture is saved in memory in one stream, then 
   we open that stream and load the picture
   ------------------------------------------------------------------------- */

STDMETHODIMP CPicShower::SetMeta(LPUNKNOWN Stream)
{
   CComQIPtr<ISTREAM> pStream = Stream;
   if(pStream) {
      // Using one smart pointer to get the Picture Dispatch

      CComPtr<IPICTUREDISP> pic;    
      LARGE_INTEGER l;
      l.QuadPart  = 0;
      pStream->Seek(l, STREAM_SEEK_SET, NULL);                                 
   
      OleLoadPicture(pStream, l.LowPart, FALSE, IID_IPictureDisp, 
                     (void **) &pic);

      if(pic) {
         _Pict = pic;              // Ok, QInterface smart pointer...

      }
      
      FireViewChange();            // Force to redraw

   }
   return S_OK;
}

Here what we receive as a parameter is one stream, then we only have to load that stream ( normally it will be in memory), and load the picture.

Let�s modify the drawing code, in order to check if the picture variable is not empty and draw it then ( in the file "PicShower.H", the method HRESULT OnDraw(ATL_DRAWINFO& di)), we set it like this:

    HRESULT OnDraw(ATL_DRAWINFO& di)
    {
      RECT& rc = *(RECT*)di.prcBounds; 
      if(_Pict) {             
         RECT r = rc;
         long lPicWidth  = 0;
         long lPicHeight = 0;                  
         if(_Pict){
            _Pict->get_Width(&lPicWidth);_Pict->get_Height(&lPicHeight);
            HRESULT hres = _Pict->Render(di.hdcDraw, 0, 0, rc.right, rc.bottom, 
                                      0, lPicHeight, lPicWidth, -(lPicHeight), &r);
         }
      } else {           
         Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom);

         SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE);
         LPCTSTR pszText = _T("PicShower: No picture assigned");
         TextOut(di.hdcDraw, 
               (rc.left + rc.right) / 2, 
               (rc.top + rc.bottom) / 2, 
               pszText, 
               lstrlen(pszText));
      }
      return S_OK;
    }

In the Render we have to use negative value -(lPicHeight), because we are working in Himetric coordinates.

Ok, now it seems that we have completed all the code for our ATL ActiveX control, let�s go for the MFC part.

Create the MFC Client:

To use our control in our MFC client App, we can do it in several ways, the two that I like best are:

  • Use Smart pointers: quite powerful and easy to work with smart pointers, it wraps all the QueryInterface/AddRef/Release for you. You can find very good articles about that, for example you can search this one in the MSDN "Calling COM Objects with Smart Interface Pointers"
  • Just import the class: This method is quite EASY, just go to the main menu of Visual Studio, and select the option "Project >> Add to Project >> Components and Controls" and from there check the folder "Registered ActiveX Controls" ( in our case we would select then the PicShower class). It creates you a wrapper class that inherits from CWnd, and all the Dispatch methods are wrapped as well as normal methods that belongs to that class... that's pretty cool to use it in one example, but in real life, is better not to use it, because you will make all your call through the IDispatch interface, and that�s quite slow, and not very professional ( we are programming with VC++ not VB Script :-) ).

Well, in the sample I use the second method ( the EASY one), just to make the test I think it's OK, but if you want to have it with Smart Pointers, just write me a line and I will do that. I suposse that we are all more or less used to MFC, so I won't explain too much how the client code works ( again if you think it would be interesting to explain it more tell me). The sample is a Dialog based application, that shows one ActiveX, and when you press the "Show Metafile" button it pass a metafile ( that I generated with a drawing tool, and then stored in the resources, load it from a file would be possible too) to the ActiveX, and it is shown.

One thing, before try the MFC client, compile the ATL project ( then the ActiveX DLL will be autoregistered and all will work fine).

About this article:

To write this article I used MSDN to search Info, and one very good article, called "Using Picture Objects in ATL", found on vbpj April 1999.

A lot of people helped me in the message board, to make this, special thanks to: Joao Vaz, Joaqu�n M L�pez Mu�oz, Mazdak, ...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here