MFC – Dynamically Creating Interactive Panels

As an exercise in dynamically creating controls and custom event handling, this article will walk through a simple thumbnail control that supports both left- and right-mouse clicks. The control will populate itself with a dynamic set of panels. The control posts two custom messages to its parent: WM_ELEMCLICK and WM_ELEMRIGHTCLICK. When the user left-clicks a panel, WM_ELEMCLICK will be posted to the parent dialog for the event. When the user right-clicks a panel, a WM_ELEMCLICK message will be posted to indicate a changing selection and a pop-up menu will be presented. If the user clicks the pop-up menu, a WM_ELEMRIGHTCLICK message will also be posted to the parent window. Any of this functionality can be easily modified based on the desired application. To keep the scope in check, each panel will be drawn as a simple, solid-colored panel. In a real application, a more sophisticated OnPaint() function will be needed to render images or dynamically draw application-specific data.

The control can handle a large number of panels. For this specific example, we are loading the control with five panels, but only displaying three at a time. The control supports a function to scroll left and right, which is called when dialog buttons are clicked. Below are a few screenshots.

The main window before any user interaction:
MFC Dynamically Creating Interactive Panels - Main Window

Notice the change in panels below as the user has selected to scroll right by one panel (by clicking the button labeled Right):
MFC Dynamically Creating Interactive Panels - Scrolling

Here, the user has left-clicked a panel. The panel has highlight itself and a left-click message has been posted to the dialog. This is indicated by the text control:
MFC Dynamically Creating Interactive Panels - Left Click

Finally, the image below shows the progression of a right-click, including the pop-up menu and events indicated by the text control:
MFC Dynamically Creating Interactive Panels - Right Click

This program consists of three primary classes:

  • CBaseStatic – This is the most fundamental of the classes. It extends a CStatic class and encapsulates a single panel in our thumbnail control. It handles the left button click and a right mouse-button up event. Interestingly, CStatic does not handle left- and right-clicks in the same way.
  • CBaseCollection – This is the control class from a user’s perspective. It also extends the CStatic MFC class. When the AddElement() function is called, a new CBaseStatic panel is dynamically created and added to the control. It tracks a vector of these panels, and determines the dynamic width of each panel by dividing its client width by the number of displayed panels. It also tracks the left-most displayed panel and uses the DisplayBases() function to handle rendering the control.
  • CDynamicWidgetDlg – This is the parent dialog used primarily for example purposes. It contains an instance of the CBaseCollection control, a CEdit control to provide feedback on handled messages, and buttons to allow scrolling back and forth.

The code is listed below.
BaseStatic.h:

#pragma once

// static controls do not fire a right button click, so we'll post this message 
// to the CBaseCollection encapsulating us
static int WM_BASERIGHTCLICK = RegisterWindowMessage(_T("BASERIGHTCLICK"));

class CBaseStatic : public CStatic
{
	DECLARE_DYNAMIC(CBaseStatic)

public:
	CBaseStatic(COLORREF col);
	virtual ~CBaseStatic();
	void SetHighlight(bool highlight);
	afx_msg void OnRButtonUp(UINT nFlags, CPoint point);

protected:
	DECLARE_MESSAGE_MAP()
	afx_msg void OnPaint();

private:
	COLORREF m_color;	// The color used to paint this panel
	bool m_highlight;	// True if this panel should draw itself as highlighted
};

BaseStatic.cpp:

#include "stdafx.h"
#include "DynamicWidget.h"
#include "BaseStatic.h"

IMPLEMENT_DYNAMIC(CBaseStatic, CStatic)

CBaseStatic::CBaseStatic(COLORREF col)
{
	m_color = col;
	m_highlight = false;
}

CBaseStatic::~CBaseStatic()
{
}

BEGIN_MESSAGE_MAP(CBaseStatic, CStatic)
	ON_WM_PAINT()
	ON_WM_RBUTTONUP()
END_MESSAGE_MAP()

// Sets the index to be highlighted (selected)
// It will be surrounded by a 3D rectangle
void CBaseStatic::SetHighlight(bool highlight)
{
	m_highlight = highlight;
}

// Paint handler
void CBaseStatic::OnPaint()
{
	CPaintDC dc(this);
	CBrush brush;
	CRect rect;

	GetClientRect(&rect);

	// This is unneeded as we are filling this entire control
	// with a color block. However, for most applications, the
	// background will need to be erased before redrawing.
	brush.CreateSolidBrush(::GetSysColor(COLOR_3DFACE));
	dc.FillRect(rect, &brush);

	// REPLACE THIS BLOCK WITH YOUR DESIRED CONTROL
	brush.CreateSolidBrush(m_color);
	dc.FillRect(rect, &brush);
	// END REPLACE

	// Paint a 3D rectangle around this control if it is selected
	if (m_highlight)
	{
		dc.Draw3dRect(rect, RGB(255, 255, 255), RGB(0, 0, 0));
	}

	CStatic::OnPaint();
}

// Right mouse button handler.
// CStatics fire a button click event, but not a right-button click.
// This supports our DIY right-button behavior
void CBaseStatic::OnRButtonUp(UINT nFlags, CPoint point)
{
	CPoint *pPoint = new CPoint(point);

	// We'll be displaying a pop-up menu, which
	// is displayed using Screen coordinates
	ClientToScreen(pPoint);

	// Send a message to the CBaseCollection class
	// with a the pop-up menu point and the CtrlID
	// for this particular panel. Both will be needed
	// by the parent CBaseCollection class.
	WPARAM wparam = (WPARAM)pPoint;
	LPARAM lparam = (LPARAM)GetDlgCtrlID();
	GetParent()->PostMessageW(WM_BASERIGHTCLICK,wparam,lparam);

	CStatic::OnRButtonUp(nFlags, point);
}

BaseCollection.h:

#pragma once
#include 
#include "BaseStatic.h"

// These two events are key to integrating our collection control with a program
// WM_ELEMCLICK = Left button click on an element
// WM_ELEMRIGHTCLICK = Right pop-up menu was selected for element
static int WM_ELEMCLICK = RegisterWindowMessage(_T("ELEMCLICK"));
static int WM_ELEMRIGHTCLICK = RegisterWindowMessage(_T("ELEMRIGHTCLICK"));

class CBaseCollection : public CStatic
{
	DECLARE_DYNAMIC(CBaseCollection)

public:
	CBaseCollection();
	virtual ~CBaseCollection();
	void SetNumVisibleElems(unsigned int numElems);
	CBaseStatic* AddElement(COLORREF col);
	int GetVisibleIndex();
	void SetVisibleIndex(int index);

protected:
	DECLARE_MESSAGE_MAP()
	afx_msg void OnElemClicked(UINT nID);
	afx_msg LRESULT OnElemRightClick(WPARAM wParam, LPARAM lParam);
	afx_msg void OnRightMenu();

private:
	typedef std::vector BASESET;

	BASESET m_bases;		// vector of CBaseStatic classes
	unsigned int m_visibleIndex;	// The index of the left-most, currently visible CBasicStatic panel in m_bases
	unsigned int m_visibleWidth;	// The width of each CBaseStatic panel, dynamically calculated
	unsigned int m_numVisibleElems;	// The number of elements to be displayed in the control at one time
	unsigned int m_selectedIndex;	// The currently selected index

	void DisplayBases();
	void SetHighlight();
};

BaseCollection.cpp:

#include "stdafx.h"
#include "DynamicWidget.h"
#include "BaseCollection.h"

// This is a range of control IDs reserved for the
// individual elements populating the collection.
#define BASE_MIN_CTRLID 5000
#define BASE_MAX_CTRLID 6000

IMPLEMENT_DYNAMIC(CBaseCollection, CStatic)

CBaseCollection::CBaseCollection()
{
	// Set our rendering variables
	// to defaults. These will all
	// be dynamically set as the 
	// user interacts with the control.
	m_visibleIndex = 0;
	m_visibleWidth = 200;
	m_selectedIndex = 0;
}

CBaseCollection::~CBaseCollection()
{
	// Cleanup all our CBaseStatic panels
	for (BASESET::iterator it = m_bases.begin(); it != m_bases.end(); it++)
	{
		delete *it;
	}
}

BEGIN_MESSAGE_MAP(CBaseCollection, CStatic)
	ON_CONTROL_RANGE(BN_CLICKED, BASE_MIN_CTRLID, BASE_MAX_CTRLID, OnElemClicked)
	ON_REGISTERED_MESSAGE(WM_BASERIGHTCLICK, OnElemRightClick)
	ON_COMMAND(ID_RIGHTMENU, OnRightMenu)
END_MESSAGE_MAP()

// Sets the number of elements visible at any one time in our collection control.
// This should be called by the main program. The elements will be equally sized
// across the width of the collection control.
void CBaseCollection::SetNumVisibleElems(unsigned int numElems)
{
	CRect clientRect;

	GetClientRect(clientRect);

	m_numVisibleElems = numElems;
	m_visibleWidth = clientRect.Width() / numElems;
}

// This appends one new element to the end of our control set.
// This function should be altered to work with more than simply a color.
CBaseStatic* CBaseCollection::AddElement(COLORREF col)
{
	CBaseStatic *newBase;
	CRect clientRect;

	GetClientRect(clientRect);

	// Each new element will start scaled to the size of our collection widget.
	// DisplayBases() will resize elements based on the number of visible elements.
	// Each control gets a unique CTRLID for event handling.
	// Notice the SS_NOTIFY attribute. This is needed to fire the messages
	// indicating mouse clicks.
	newBase = new CBaseStatic(col);
	newBase->Create(NULL, WS_CHILD | SS_NOTIFY, clientRect, this, BASE_MIN_CTRLID + m_bases.size());
	m_bases.push_back(newBase);

	DisplayBases();

	return newBase;
}

// Returns the left-most visible element
int CBaseCollection::GetVisibleIndex()
{
	return m_visibleIndex;
}

// Sets the left-most visible element
void CBaseCollection::SetVisibleIndex(int index)
{
	if (index = (int)(m_bases.size()-m_numVisibleElems))
	{
		m_visibleIndex = m_bases.size() - m_numVisibleElems;
	}
	else
	{
		m_visibleIndex = (unsigned int)index;
	}

	DisplayBases();
}

// Renders all the elements
void CBaseCollection::DisplayBases()
{
	CRect clientRect, baseRect;

	GetClientRect(clientRect);

	// Loop through all the added elements
	unsigned int index = 0;
	for (BASESET::iterator it = m_bases.begin(); it != m_bases.end(); it++, index++)
	{
		// m_visibleElements indicates the first visible element.
		// If the current index is within the visible indexes, show it.
		// Otherwise, hide it.
		if (index >= m_visibleIndex && index MoveWindow(baseRect, FALSE);
			(*it)->ShowWindow(SW_SHOW);
		}
		else
		{
			(*it)->ShowWindow(SW_HIDE);
		}
	}

	Invalidate();
}

// This tells the selected element to highlight itself.
void CBaseCollection::SetHighlight()
{
	int index = 0;
	for (BASESET::iterator it = m_bases.begin(); it != m_bases.end(); it++, index++)
	{
		(*it)->SetHighlight(m_selectedIndex == index);
	}
}

// Left-click handler fired by an individual element.
// This function highlights the selected element and
// posts an event to the parent for the click event.
void CBaseCollection::OnElemClicked(UINT nID)
{
	int index = 0;
	for (BASESET::iterator it = m_bases.begin(); it != m_bases.end(); it++, index++)
	{
		if (nID == (*it)->GetDlgCtrlID())
		{
			m_selectedIndex = index;
			GetParent()->PostMessage(WM_ELEMCLICK, NULL, (LPARAM)m_selectedIndex);
		}
	}

	SetHighlight();
	Invalidate();
}

// Right-click handler fired by an individual element.
// This function fires a left-click event and presents a pop-up menu
LRESULT CBaseCollection::OnElemRightClick(WPARAM wParam, LPARAM lParam)
{
	CMenu popup;
	CPoint *point = (CPoint*)wParam;
	int ctrlID = lParam;

	// we just selected an element, so fire the click event
	// this may be undesirable in some applications
	OnElemClicked(ctrlID);

	// display a pop-up menu
	popup.CreatePopupMenu();
	popup.AppendMenu(MF_STRING, ID_RIGHTMENU, _T("Click Me!"));
	popup.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point->x, point->y, this);

	return TRUE;
}

// Pop-up menu click handler. Passes an event to the parent.
void CBaseCollection::OnRightMenu()
{
	GetParent()->PostMessage(WM_ELEMRIGHTCLICK, NULL, (LPARAM)m_selectedIndex);
}

DynamicWidgetDlg.h:

#pragma once
#include "BaseCollection.h"

class CDynamicWidgetDlg : public CDialogEx
{
public:
	CDynamicWidgetDlg(CWnd* pParent = NULL);

#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_DYNAMICWIDGET_DIALOG };
#endif

protected:
	DECLARE_MESSAGE_MAP()
	virtual void DoDataExchange(CDataExchange* pDX);
	virtual BOOL OnInitDialog();
	afx_msg LRESULT OnCollectionClick(WPARAM wParam, LPARAM lParam);
	afx_msg LRESULT OnCollectionRightClick(WPARAM wParam, LPARAM lParam);
	afx_msg void OnClickedLeft();
	afx_msg void OnClickedRight();

private:
	CBaseCollection m_baseCollection;	// Our new control
	CEdit m_text;				// Used to indicate to the user that an event occurred
	CButton m_left, m_right;		// Buttons to allow scrolling left and right in the CBaseCollection control
};

DynamicWidgetDlg.cpp:

#include "stdafx.h"
#include "DynamicWidget.h"
#include "DynamicWidgetDlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

CDynamicWidgetDlg::CDynamicWidgetDlg(CWnd* pParent)
	: CDialogEx(IDD_DYNAMICWIDGET_DIALOG, pParent), m_baseCollection()
{
}

void CDynamicWidgetDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_IMG, m_baseCollection);
	DDX_Control(pDX, IDC_OUTTEXT, m_text);
	DDX_Control(pDX, IDC_LEFT, m_left);
	DDX_Control(pDX, IDC_RIGHT, m_right);
}

BEGIN_MESSAGE_MAP(CDynamicWidgetDlg, CDialogEx)
	ON_REGISTERED_MESSAGE(WM_ELEMCLICK, OnCollectionClick)
	ON_REGISTERED_MESSAGE(WM_ELEMRIGHTCLICK, OnCollectionRightClick)
	ON_BN_CLICKED(IDC_LEFT, OnClickedLeft)
	ON_BN_CLICKED(IDC_RIGHT, OnClickedRight)
END_MESSAGE_MAP()

BOOL CDynamicWidgetDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	m_text.SetReadOnly(TRUE);

	// set up our collection widget, only 3 elements are visible at a time
	m_baseCollection.SetNumVisibleElems(3);
	
	// loop through 5 elements and add them, making each a random color
	// you'll probably want to do something more interesting than this
	for (int i = 0; i < 5; i++)
	{
		m_baseCollection.AddElement(RGB(rand() % 255, rand() % 255, rand() % 255));
	}

	return TRUE;
}

// Program-level button click handler fired by clicking on a single element
LRESULT CDynamicWidgetDlg::OnCollectionClick(WPARAM wParam, LPARAM lParam)
{
	int selectedIndex = (int)lParam;

	wchar_t sz[500];
	wsprintfW(sz, _T("Dialog captured left click for index %d!"), selectedIndex);
	m_text.SetWindowTextW(sz);

	return TRUE;
}

// Program-level right-click handler fired by clicking on a single element
LRESULT CDynamicWidgetDlg::OnCollectionRightClick(WPARAM wParam, LPARAM lParam)
{
	int selectedIndex = (int)lParam;

	wchar_t sz[500];
	wsprintfW(sz, _T("Dialog captured pop-up menu click for index %d!"), selectedIndex);
	m_text.SetWindowTextW(sz);

	return TRUE;
}

// "Left" button handler
void CDynamicWidgetDlg::OnClickedLeft()
{
	m_baseCollection.SetVisibleIndex(m_baseCollection.GetVisibleIndex() - 1);
}

// "Right" button handler
void CDynamicWidgetDlg::OnClickedRight()
{
	m_baseCollection.SetVisibleIndex(m_baseCollection.GetVisibleIndex() + 1);
}

Finally, for reference purposes, here is the generated DynamicWidget.rc file:

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#ifndef APSTUDIO_INVOKED
#include "targetver.h"
#endif
#include "afxres.h"
#include "verrsrc.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// English (United States) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#ifndef APSTUDIO_INVOKED\r\n"
    "#include ""targetver.h""\r\n"
    "#endif\r\n"
    "#include ""afxres.h""\r\n"
    "#include ""verrsrc.h""\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "#define _AFX_NO_SPLITTER_RESOURCES\r\n"
    "#define _AFX_NO_OLE_RESOURCES\r\n"
    "#define _AFX_NO_TRACKER_RESOURCES\r\n"
    "#define _AFX_NO_PROPERTY_RESOURCES\r\n"
    "\r\n"
    "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n"
    "LANGUAGE 9, 1\r\n"
    "#include ""res\\DynamicWidget.rc2""  // non-Microsoft Visual C++ edited resources\r\n"
    "#include ""afxres.rc""      // Standard components\r\n"
    "#if !defined(_AFXDLL)\r\n"
    "#include ""afxribbon.rc""   // MFC ribbon and control bar resources\r\n"
    "#endif\r\n"
    "#endif\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDR_MAINFRAME           ICON                    "res\\DynamicWidget.ico"

/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_DYNAMICWIDGET_DIALOG DIALOGEX 0, 0, 453, 153
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "DynamicWidget"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
    PUSHBUTTON      "Cancel",IDCANCEL,396,132,50,14
    CONTROL         "",IDC_IMG,"Static",SS_BLACKFRAME,19,18,414,87
    EDITTEXT        IDC_OUTTEXT,19,115,208,14,ES_AUTOHSCROLL
    PUSHBUTTON      "Left",IDC_LEFT,234,115,50,14
    PUSHBUTTON      "Right",IDC_RIGHT,287,115,50,14
END


/////////////////////////////////////////////////////////////////////////////
//
// Version
//

VS_VERSION_INFO VERSIONINFO
 FILEVERSION 1,0,0,1
 PRODUCTVERSION 1,0,0,1
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x40004L
 FILETYPE 0x1L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040904B0"
        BEGIN
            VALUE "CompanyName", "TODO: "
            VALUE "FileDescription", "DynamicWidget"
            VALUE "FileVersion", "1.0.0.1"
            VALUE "InternalName", "DynamicWidget.exe"
            VALUE "LegalCopyright", "TODO: (c) .  All rights reserved."
            VALUE "OriginalFilename", "DynamicWidget.exe"
            VALUE "ProductName", "TODO: "
            VALUE "ProductVersion", "1.0.0.1"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x409, 1200
    END
END


/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_DYNAMICWIDGET_DIALOG, DIALOG
    BEGIN
        LEFTMARGIN, 7
        RIGHTMARGIN, 446
        TOPMARGIN, 7
        BOTTOMMARGIN, 146
    END
END
#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// AFX_DIALOG_LAYOUT
//

IDD_DYNAMICWIDGET_DIALOG AFX_DIALOG_LAYOUT
BEGIN
    0
END

#endif    // English (United States) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
#define _AFX_NO_SPLITTER_RESOURCES
#define _AFX_NO_OLE_RESOURCES
#define _AFX_NO_TRACKER_RESOURCES
#define _AFX_NO_PROPERTY_RESOURCES

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE 9, 1
#include "res\DynamicWidget.rc2"  // non-Microsoft Visual C++ edited resources
#include "afxres.rc"      // Standard components
#if !defined(_AFXDLL)
#include "afxribbon.rc"   // MFC ribbon and control bar resources
#endif
#endif

/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED
Advertisements

Printing with Adobe Reader and Waiting for it to Finish (using DDE)

If you program in Visual Studio, there is a nice ActiveX plugin available for Adobe Reader that provides basic capabilities such as loading, displaying, and printing a PDF. After quickly integrating this plugin into one of my projects and happily prepping to begin silently printing PDFs on the user’s command, I realized a nearly show-stopping issue with the API – there is no blocking and no return! I needed to have the application wait until printing was completed before continuing processing. Most of the available API calls through the ActiveX are essentially fire-and-forget in nature, meaning you can easily launch printing, but you’ll have no idea when it completes. After some extensive searching, I found that Adobe is still supporting the older Dynamic Data Exchange (DDE) interface, which does enable blocking on a process – specifically printing. Apparently this has become the de facto approach to printing with blocking.

An older version of the Interapplication Communication documentation is available here. In summary, the following commands are available for Adobe Reader:

● AppExit
● CloseAllDocs
● DocClose
● DocGoTo
● DocGoToNameDest
● DocOpen
● FileOpen
● FileOpenEx
● FilePrint
● FilePrintEx
● FilePrintSilent
● FilePrintSilentEx
● FilePrintTo
● FilePrintToEx

Below is a quick example of how to communicate with Adobe Reader X from a Visual C++ program using DDE. Of particular note is the [FilePrint("test.pdf")][AppExit()] string and the si.nShow = SW_HIDE | SW_MINIMIZE command, which enable silent printing of test.pdf. This will be visible to the user as Adobe Reader launches in a minimized state, prints the specified file using the default settings, and exits. Additionally, DdeClientTransaction( (LPBYTE)hData, -1, hConv, 0L, 0, XTYP_EXECUTE, 3600000, 0 ) is the key call with DDE that enables blocking when waiting for Adobe Reader to finish. The 3600000 is measured in milliseconds, meaning the program will wait 30 seconds before timing out.

#include "ddeml.h"


CString acroReadExe = "C:\\Program Files (x86)\\Adobe\\Reader 10.0\\Reader\\AcroRd32.exe";
CString stQuery = "[FilePrint(\"test.pdf\")][AppExit()]";


HSZ hszApp( 0 );
HSZ hszTopic( 0 );
DWORD dwIdInst = 0;
HCONV hConv = 0;
SHELLEXECUTEINFO si = { sizeof( SHELLEXECUTEINFO ) };
HDDEDATA hData;


//////////////////////////////////////////
// You'll need a couple support functions
//////////////////////////////////////////

inline ProcessDdeMessages()
{
  MSG msg;
 
  while( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
  {
    TranslateMessage( &msg );
    DispatchMessage( &msg );
  }
}


HDDEDATA CALLBACK DdeCallback(
  UINT uType,
  UINT uFmt,
  HCONV hconv,
  HSZ hsz1,
  HSZ hsz2,
  HDDEDATA hdata,
  DWORD dwData1,
  DWORD dwData2 )
{
  return 0;
}




////////////////////////////////////
// Here is the core of the code
////////////////////////////////////

// Initialize DDE
if( DMLERR_NO_ERROR != DdeInitialize( &dwIdInst, (PFNCALLBACK)DdeCallback, APPCLASS_STANDARD | APPCMD_CLIENTONLY, 0 ) )
{
  AfxMessageBox( "Unable to initialize DDE" );
}

hszApp = DdeCreateStringHandle( dwIdInst, ddeServerName, 0 );
hszTopic = DdeCreateStringHandle( dwIdInst, "control", 0 );


// Connect to DDE Server
if( !( hConv = DdeConnect( dwIdInst, hszApp, hszTopic, 0 ) ) )
{
  // Start the DDE server and try connect again
  si.lpVerb = "open";
  si.lpFile = acroReadExe;
  si.nShow = SW_HIDE | SW_MINIMIZE;
 
  if( ShellExecuteEx( &si ) )
  {
    do
    {
      ProcessDdeMessages();
      Sleep( 300 );
    }
    while( !( hConv = DdeConnect( dwIdInst, hszApp, hszTopic, 0 ) ) );
  }
}
 
DdeFreeStringHandle( dwIdInst, hszApp );
DdeFreeStringHandle( dwIdInst, hszTopic );
 

// If we successfully connected, submit our query
if( !dwIdInst || !hConv )
{
  AfxMessageBox( "DDE Connection Failed to " + acroReadExe );
}
  
hData = DdeCreateDataHandle( dwIdInst, (LPBYTE)( stQuery.GetBuffer( stQuery.GetLength() + 1 ) ), stQuery.GetLength(), 0, 0, CF_TEXT, 0 );

if( !hData )
{
  AfxMessageBox( "ADOBE READER: Command failed: \"" + stQuery + "\"" );
}
 
// Execute the query and wait
DdeClientTransaction( (LPBYTE)hData, -1, hConv, 0L, 0, XTYP_EXECUTE, 3600000, 0 );


//  Disconnect DDE
if( hConv )
{
  DdeDisconnect( hConv );
  hConv = 0;
}
      
if( dwIdInst )
{
  DdeUninitialize( dwIdInst );
  dwIdInst = 0;
}

Reading an OCCI BLOB in C++

Example of how to read an OCCI Blob after retrieving it from the database.

CString fileName = "OutFile.bin";
int size;
FILE *destFile;
Blob blob;

// You'll need to make a database call here to populate the blob

fopen( &destFile, fileName, "wb" );
std::vector< BYTE > buffer( blob.getChunkSize() );
Stream *stream = blob.getStream();
 
if( destFile != NULL )
{
 
  while ( -1 != ( size = stream->readBuffer( ( char * ) &buffer[0], buffer.size() ) ) )
  {
    fwrite( ( char * ) &buffer[0], 1, size, destFile );
  }
 
blob.closeStream( stream );
fclose( destFile );