Click here to Skip to main content
Click here to Skip to main content

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

By , 12 Mar 2002
 

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

About the Author

Braulio Dez
Web Developer
Spain Spain
Member
Braulio is a developer specialized on Ms Technologies (Silverlight, ASP .net, SSRS, SSAS, SSIS). Currently he is working for Avanade in Málaga and as a journalist for the Spanish .net magazine Dotnetmania. He also is the webmaster of an specializad in .net technologies site Tipsdotnet.com, and Silverlight Based DB Schema Editor (www.dbschemaeditor.com).

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionHow to ROTATE image, using an IPicturememberAlex Evans3 Jan '05 - 16:34 
Any idea, how can I ROTATE 90, 180 etc using an IPicture object, once it is loaded and showing on screen...
 
Thanks
Alex
Questionhow to export picture control into bmp filemembernnvidya2 Jun '04 - 20:37 
can anybody tell me how to export picture control into bmp file or jpg and html file format
 
vidya
GeneralCool samplesussAnonymous13 Oct '03 - 2:21 
Hi Braulio,
 
Your sample is really helpful.
 
Cheers
Ishwar
GeneralGetting bits pointer from IPicturesussCodeSilver22 May '03 - 15:14 
How can I retrieve the bits pointer using IPicture?Confused | :confused: I had look thru msdn and can't find a hint on doing so. Please help. OMG | :OMG:
 
Crystal Silver Codes
QuestionUsing Smart Pointers?sussCodeSilver21 May '03 - 0:28 
I had tried using the IDispatch interface. It works fine. I wanted to try using smart pointers instead. But theres a problem here. I insert m_pic->CreateInstance() into OnInitDialog() to create the object. However I cannot see the object on the screen when I run it. I know i should be missing something. Can someone give a helping hand? How can I make the object visible on the dialog?
 
Crystal Silver Codes
AnswerRe: Using Smart Pointers?memberBraulio Díez21 May '03 - 0:38 
Hi,
 
If you are using smart pointer, you don't need to call CreateInstance ( If I remember well Smile | :) ).
 
HTH
Braulio
QuestionHow can I send picture from my ActiveX component into VB project ?memberalexangr13 May '03 - 23:21 
How can I send picture from my ActiveX component written in VC6 into Image component in VB project ?
Generalsend a bitmapmemberTodd.Harvey9 Sep '02 - 6:19 
at first, I didn't know what a metafile was, and what I really wanted was just to send a bitmap to a control.
Maybe sending metafiles is superior, but here is how to send a bitmap:
 

void CDonutDemoDlg::PassImage( CBitmap *pInBitmap )
{
ASSERT( pInBitmap );
 

BITMAP pBM;
pInBitmap->GetBitmap( &pBM ) ;
 
// from code guru http://www.codeguru.com/forum/showthread.php?s=&threadid=163685&highlight=ipicture
PICTDESC desc;
HRESULT hres;
 

// how make a picture object
// setup pictdesc structure
desc.cbSizeofstruct = sizeof(PICTDESC) ;
desc.picType = PICTYPE_BITMAP;
desc.bmp.hbitmap = (HBITMAP)pInBitmap->GetSafeHandle();
LPPICTURE pPicture = 0;
 
// create the StdPicture object
hres = ::OleCreatePictureIndirect(
&desc ,
IID_IPicture,
FALSE, // don't destroy it
(LPVOID*)&pPicture);
 
ASSERT( 0 != pPicture);
ASSERT( 0 == hres ); // all the error checking I've got here just now
 

m_Donut.SetImage( pPicture );
} // PassImage
 



GeneralRe: send a bitmapmemberTodd.Harvey9 Sep '02 - 6:22 
oops, I should have pointed out that m_Donut has some of Braulio's code grafted into it, and SetImage is like one of his subroutines that takes an LPUNKNOWN.
GeneralRe: send a bitmapmemberRamon80k17 Jan '03 - 0:28 
How can I use this picture from visual basic?
For example, show it in a picture box with PaintPicture method. Seems I can access to width, height, but it not displays.
 
Thank's
GeneralRe: send a bitmapmemberTodd.Harvey17 Jan '03 - 3:39 
Ramon,
I think I've got that on another PC which I can't get to just now. Isn't there an image control in VB? or else was it Brasilio's control that just "worked"? It's been about 6 months for me now ...
looking a bit at his article, I see the line "IPicture? It´s the interface to a standard component that allows you to display Metafiles, Bitmaps ( jpg, gif, tiff...). Cool isn´t it ?. "
I think you might be able to put an image control in your vb app, and also include Braulio's control, and then set your image control .picture property to the .picture property of Braulio's control.
 
I was trying to go backwards, and the syntax was:
Call PicShower.SetDirectMeta (Image1.Picture)
and it didn't work in JavaScript.
 
Sorry, best I can do at the moment.
GeneralJavaScript or VBScript syntax questionmemberTodd.Harvey22 Aug '02 - 10:01 
so, for example I put your control in a web page, and suppose I have an IMG in that page. Is there some property of the IMG that I can pass to your control?
Or otherwise, do you know how to script your control and set the image (SetDirectMeta) in either JavaScript or VBScript?
Cry | :((
GeneralRe: JavaScript or VBScript syntax questionmemberBraulio Díez22 Aug '02 - 21:36 
Hi,
 
I know with VBScript you can play around with ActiveX controls, and maybe you can manage to get the image somehow using VBScript... sorry I haven´t worked that much with VB Script, but I think it should be possible.
 
Good luck
Braulio
GeneralMFC / Gypsy KingsmemberTodd.Harvey21 Aug '02 - 16:06 
thanks much Braulio, I was really struggling with a similar problem, and your article really helped me
 
I have an MFC activex control and imitated your article to load a bitmap from a vb test project.
I used the smart pointer for the image _Pict as you did, and included .
Then
set_Image(LPUNKNOWN pIn)
{
_Pict = pIn;
}
and it worked.
 
The Gypsy Kings are what get me out of a slump. If I'm tired in the late afternoon, or sick of MFC or something I don't understand, I can count on the Gypsy Kings to blow the fog away. Maybe they're popular where you are (maybe they are a little outdated . . .)
GeneralRe: MFC / Gypsy KingsmemberBraulio Díez21 Aug '02 - 21:39 
Hi Todd !
 
Thanks to you for the comment, it's always good to know that something that one makes it's useful for somebody Wink | ;-)
 
The approach that you use, it's good if you use the ActiveX inside your application ( as a DLL), but if your ActiveX it's an EXE activeX, mmmm.... then you have to use the painfull long way ( because sharing Dc's along different process spaces it's not a very good idea).
 
Good luck with your development !, so long !
 
Braulio
Generalloading a bmp file in CBitmap objectsusssaima shafiq12 Jul '02 - 20:15 
hi!
i wanna load a bmp file in a Cbitmap object using its name stored in a CString object.Is thee any method available for this?
 
Confused | :confused:
GeneralRe: loading a bmp file in CBitmap objectmemberChristian Graus12 Jul '02 - 21:30 
::LoadImage

 
Christian
 
come on all you MS suckups, defend your sugar-daddy now. - Chris Losinger - 11/07/2002

QuestionHow knowsmemberAnonymous2 Apr '02 - 23:55 
// Description : Loads a picture from given file
// Return type : BOOL
// Argument : LPCTSTR szFileName
// Argument : IPictureDisp** ppPicture
BOOL LoadPicture( LPCTSTR szFileName, IPictureDisp** ppPictureDisp )
{
BOOL bResult = FALSE;
if ( szFileName )
{
OFSTRUCT of;
HANDLE hFile = NULL;;
#ifdef _UNICODE
USES_CONVERSION;
if ( (hFile = (HANDLE)OpenFile( W2A(szFileName), &of, OF_READ | OF_SHARE_COMPAT)) != (HANDLE)HFILE_ERROR )
#else
if ( (hFile = (HANDLE)OpenFile( szFileName, &of, OF_READ | OF_SHARE_COMPAT)) != (HANDLE)HFILE_ERROR )
#endif
{
*ppPictureDisp = NULL;
DWORD dwHighWord = NULL, dwSizeLow = GetFileSize( hFile, &dwHighWord );
DWORD dwFileSize = dwSizeLow;
HRESULT hResult = NULL;
if ( HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize) )
if ( void* pvData = GlobalLock( hGlobal ) )
{
DWORD dwReadBytes = NULL;
BOOL bRead = ReadFile( hFile, pvData, dwFileSize, &dwReadBytes, NULL );
GlobalUnlock( hGlobal );
if ( bRead )
{
CComPtr spStream;
_ASSERTE( dwFileSize == dwReadBytes );
if ( SUCCEEDED( CreateStreamOnHGlobal( hGlobal, TRUE, &spStream) ) )
if ( SUCCEEDED( hResult = OleLoadPicture( spStream, 0, FALSE, IID_IPictureDisp, (void**)ppPictureDisp ) ) )
bResult = TRUE;
}
}
CloseHandle( hFile );
}
}
return bResult;
}
 
Mike
www.exontrol.com
AnswerRe: How knowssusssaima shafiq12 Jul '02 - 21:12 
Thanx a lot dear
God Bless USmile | :)
GeneralThanks CrishmemberAnonymous17 Mar '02 - 19:35 
Thanks crish for giving so good explanations and for clearing our concepts.
 
Thanks a lot;)
GeneralCrish ??memberBraulio Díez18 Mar '02 - 3:43 
Oops... sorry this article was made by me ( Braulio), not "Crish"... Wink | ;-)
 
Anyway you're wellcome
GeneralRe: Crish ??memberAnonymous2 Apr '02 - 23:51 
Really?
GeneralIt's easy,but very useful, Thanks!memberAnonymous13 Mar '02 - 17:29 
Smile | :)
 
linghuye
GeneralATL7+Dumb Question ;)memberMazdak13 Mar '02 - 4:44 
Hi Braulio:
I compiled control in VC7,and there were no error,but you made some mistake in your article text.You use IPICTURE,ISTREAM,IPICTUREDISP in yout text somewhere.The correct form is IPicture,IStream,IPictureDisp as you know.
 
ANd I want to ask very dumb question,your demo project is not available so give me one line of code
about MFC client:
 
PicshowerCtrl.SetMeta(?????Here is what????);
PicshowerCtrl.SetDirectMeta(?????Here is what????);
 
Wink | ;)
 
Thanx
 
Bye
 

 
<html>Mazy</html>
 
"So,so you think you can tell,
Heaven from Hell,
Blue skies from pain,...
How I wish,how I wish you were here."
Wish You Were Here-Pink Floyd-1975


GeneralRe: ATL7+Dumb Question ;)memberBraulio Díez13 Mar '02 - 4:54 
Hi !
 
Now you can download the demo project, Chris solved the problem ( so you can have the MFC client and server).
 
The interface name, I think in VC6 is uppercase maybe change in VC7.
 
About the MFC, I used:
#include < comdef.h >
 
and
 
/* -------------------------------------------------------------------------
   Here, we load the metafile, and we send it to the ActiveX control
   ( here we send it through the stream method, but you could use the
   direct method because we are in the same process space)
   ------------------------------------------------------------------------- */
void CMFCClientDlg::OnSetmeta() 
{
   // First load the metafile from the resources... ( you can load the metafile from a file if you want)
   HRSRC   hResInfo = FindResource(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_METAFILE), "Metafile");   
   HGLOBAL hRes = LoadResource(AfxGetResourceHandle(), hResInfo);      
   ASSERT(hRes);   
 
   // ---- Try to create the picture object...
   BYTE *lpMF = (BYTE *) LockResource(hRes);
   PICTDESC pd = {sizeof(PICTDESC), PICTYPE_ENHMETAFILE};
   CRect    rcPaint(10, 10, 10, 10);
 
   HENHMETAFILE hMeta =  SetEnhMetaFileBits(
                        SizeofResource(AfxGetResourceHandle(), hResInfo),
                        lpMF);
 

 
   pd.emf.hemf  = hMeta; // hEnhMetaFile;      
       
   IPicture *IPict = NULL;
   HRESULT Res = OleCreatePictureIndirect(&pd, 
                                    IID_IPicture,
                                    FALSE,
                                    (LPVOID *)&IPict);
   
   if(IPict) {                        
      // Here we could call...
      //m_axPicShower.SetDirectMeta(IPict);
      
      // But we are going to use here the harder method
      // use this when you are in a different process space
      // b.g.: EXE server, or handling your ActiveX control
      // that is pasted in an Automated version of Word, or Excel...      
      SendPicture(IPict);      
   }		
   IPict->Release();
   UnlockResource(hRes);
   FreeResource(hRes);
	
}
 
/* -------------------------------------------------------------------------
      Send a picture to the ATL ActiveX ( need to save it into an stream
   to send it to the server, when they are in different process spaces
   (Exe Server or, App ->( Excel/Word+DLL)), and there are problems with 
   the HDC)
   ------------------------------------------------------------------------- */
 
void CMFCClientDlg::SendPicture(IPicture *pPicture) 
{
   if(pPicture) {
      IPersistStreamPtr p = pPicture;
      if(p) {
         ULARGE_INTEGER l;
         p->GetSizeMax(&l);
         HGLOBAL hGlob = GlobalAlloc(GHND, l.LowPart);
         if(hGlob) {
            IStreamPtr spStream;
            CreateStreamOnHGlobal(hGlob, TRUE, &spStream);
            if(spStream) {
               if(SUCCEEDED(p->Save(spStream, FALSE)))  {                 
                  m_axPicShower.SetMeta(spStream);                  
                  m_axPicShower.Invalidate();
               }
               spStream.Release();
            }
            GlobalFree(hGlob);
         }
      }
   }
}
 
But now you can download it ( the zip includes both the MFC and the ATL thing), thanks for the comment, Bye !
Braulio

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 13 Mar 2002
Article Copyright 2002 by Braulio Dez
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid