Using ODBC to Access Image Data (C++ 4.0 and later)

This lesson describes how to use the LEADTOOLS ODBC features to maintain images in a table with other data. In this lesson, you accomplish the following:

Note: The LEADTOOLS ActiveX requires ODBC drivers that can properly handle long binary fields. Examples have been tested and will work with version 3.0 of the Microsoft ODBC Desktop Driver Pack. (This driver pack ships with Access 95 and works on 32-bit systems.) You can download drivers from the LEAD BBS, the CompuServe forum, or the Worldwide Web home page. The CompuServe forum is at GO LEADTECH. The web page is ftp://ftp.leadtools.com/pub/utils/ODBC32.zip. The file to download is ODBC32.ZIP.

1. Use a database manager to create an ACCESS MBD database with one table that has two fields. Use the following specifications:

a. Database name: leadpic.mdb.

b. Table name: people.

c. Field name: who. Data Type: text. Size: 255.

d. Field name: photo. Data Type: Long Binary. Size: N/A.

2. Create an ODBC data source that references the same database follows:

a. From the Windows Control Panel, open the ODBC administrator and click the Add button.

b. Select Microsoft Access driver and click the OK button.

c. Enter the name LEADODBC.

d. Click the Select directory button and select the path to the database that you created.

e. Click the OK button; click the next OK button; then click the Close button.

3. Start the Microsoft Developer Studio for Visual C++, version 4.0.

4. Select the File >New menu option, select Project Workspace, and click the OK button.

5. In the New Project Workspace dialog box, do the following:

a. In the Type list box, select MFC AppWizard (exe).

b. In the Name text box, specify ltcdb.

c. In the Location text box, specify the path of the project.

d. In the Platforms list box, check Win32.

e. Click the Create button.

6. In the Step 1 dialog box, do the following:

a. Select Single document.

b. Click the Next button.

7. In the Step 2 of 6 dialog box, do the following:

a. Select Database view without file support.

b. Click the Data Source button.

c. Select DAO and use the browser to specify the leadpic.mdb database that you created earlier.

d. Ensure that Dynaset and Detect dirty columns are selected and click the OK button.

e. Select the people table and click the OK button.

f. Click the Next button.

8. In the Step 3 of 6 dialog box, do the following:

a. For OLE compound document support, ensure that None is selected.

b. For OLE support, select OLE Controls.

c. Click the Next button.

9. In the Step 4 of 6 dialog box, do the following:

a. For supported features, select only Docking toolbar and 3D controls.

b. Click the Next button.

10. In the Step 5 of 6 dialog box, do the following:

a. For comments, ensure that Yes, Please is selected.

b. For how to use the MFC library, select As a statically linked library.

c. Click the Next button.

11. In the Step 6 of 6 dialog box, just click Finish.

12. Read New Project Information, and click OK. (The AppWizard creates the project files and opens the project.)

13. Add the L_OCX.H and L_OcxErr.H files, which define LEAD constants, to your project as follows:

a. Copy the \lead\include\L_OCX.H and l_ocxerr.h files to your project directory.

b. In the Project Workspace, click the FileView tab.

c. Double-click the ltcdb files folder to open it.

d. Double-click the Dependencies folder to open it.

e. Double-click the StdAfx.h file to edit it.

f. Add the following line to the end of the file:

#include "L_OCX.H"

14. Add a LEAD control to the Controls toolbar as follows:

a. In the Project Workspace, click the ResourceView tab.

b. Double-click the ltcdb resources folder to open it.

c. Double-click the Dialog folder to open it.

d. Double-click IDD_LTCDB_FORM to design the form.

e. Select the TODO... text control; then press the Delete key to delete it.

f. From the main menu, select Insert > Component. (The Component Gallery appears.)

g. Click the OLE Controls tab.

h. Double-click the LEAD Control icon. (The Confirm Classes dialog box appears.)

i. Ensure that both CLead and CPicture are checked.

j. Click OK to complete the selection; then click Close to close the Component Gallery. (The LEAD control appears in the Controls toolbar.)

15. Add the LEAD control to your form as follows:

a. image\btnlead.gif Click the LEAD control icon; then click and drag on the form to size and position the control.

b. Double-click the new LEAD control to edit its properties.

c. Change the ID to IDC_LEAD1 and change the BorderStyle property to 1 - Fixed Single.

16. Add an edit box for the image name and bind it to the database as follows:

a. Click the Edit Box icon; then click and drag on the form to size and position the control.

b. Double-click the new Edit Box control to edit its properties.

c. Change the ID to IDC_Cwho.

d. Hold down the Ctrl key and double click the control.

e. For the Member variable name, select m_pSet->m_who, and click the OK button.

17. Do the following to add m_Lead1 to the CLtcdbView class and link the variable to the LEAD control using dynamic data exchange:

a. Open the ClassWizard (CTRL-W).

b. Click the Member Variables tab.

c. In the Class Name box, select CLtcdbView.

d. In the Control IDs list, select IDC_LEAD1.

e. Click the Add Variable... button.

f. Specify m_Lead1 as the variable name, and Control as the category.

g. Click OK to close the dialog box, and click OK to close the MFC ClassWizard.

18. Edit LTCDBVIEW.H and insert the following lines in the definition of CLtcdbView after DECLARE_MESSAGE_MAP():

// These variables let us synchronize the LEAD control's ODBC access with the
// the normal database access
long OldPosition, NewPosition;

// This variable lets us make the dbMove method conditional
BOOL NormalMove;

19. Edit the CLtcdbView::OnInitialUpdate() function and add the following code to the end of the function:

// Turn off automatic scrolling and repainting
m_Lead1.SetAutoScroll(FALSE);
m_Lead1.SetAutoRepaint(FALSE);

// Open the database.
// Use the same SELECT statement that is used for the data control.
// Note that the image field must be listed first in the SELECT statement.
int nRet = m_Lead1.dbOpen("ODBC;DSN=LEADODBC", "SELECT photo FROM people ORDER BY who", "photo", DB_OPENOPTIONS_NONE);

// Set the LEAD control's database properties
m_Lead1.SetDbLoadBits(24);
m_Lead1.SetDbLockingMode(DB_LOCKINGMODE_OPTIMISTIC);

// Enable use of the dbMove method in the data control's Reposition event
NormalMove = TRUE;
NewPosition = 0;

20. Disable the DAO recordset's access of the photo field. (We will access this field through the LEAD control.) To disable the recordset's access, do the following:

a. Edit the CLtcdbSet::CLtcdbSet function to change m_nFields = 2 to m_nFields = 1.

b. Edit the CLtcdbSet::DoFieldExchange function and remove the following statement: DFX_LongBinary(pFX, _T("[photo]"), m_photo);

c. Edit the CLtcdbSet.h file to remove the following line: CLongBinary m_photo;

21. Edit the CLtcdbSet::GetDefaultSQL function to use an SQL SELECT statement as follows:

CString CLtcdbSet::GetDefaultSQL()
{
  return "SELECT who FROM people ORDER BY who";
}

22. Add the LEAD control's Change event as follows:

a. Press Ctrl-W to go to the MFC Class Wizard.

a. Click the Message Maps tab.

b. In the Class Name combo box, select CLtcdbView.

c. In the Object IDs list box, select IDC_LEAD1.

d. In the Messages list box, select Change.

e. Click the Add function button. Choose OK for the default function name (OnChangeLead1).

f. Click the Edit Code button, and edit the function so that it appears as follows:

void CLtcdbView::OnChangeLead1() 
{
  // Avoid sizing images that are not loaded.
  if (m_Lead1.GetBitmap() == 0)
  {
    m_Lead1.ForceRepaint(); // So we can see when images are missing.
    return;
  }

  // Calculate the display rectangle to fit the image inside the control
  float HeightAllowed = m_Lead1.GetScaleHeight();
  float WidthAllowed = m_Lead1.GetScaleWidth();
  float XProportion = m_Lead1.GetBitmapWidth();
  float YProportion = m_Lead1.GetBitmapHeight();
  float DisplayTop;
  float DisplayHeight;
  float DisplayWidth;
  float DisplayLeft;

  // See if using the allowed height makes the image too wide.
  if ((HeightAllowed * XProportion / YProportion) <= WidthAllowed)
  {   // Use the allowed height.
    DisplayTop = (float)0;
    DisplayHeight = HeightAllowed;
    DisplayWidth = DisplayHeight * XProportion / YProportion;
    DisplayLeft = (WidthAllowed - DisplayWidth) / 2;
  }  
  else
  {   // Use the allowed width.
    DisplayLeft = (float)0;
    DisplayWidth = WidthAllowed;
    DisplayHeight = DisplayWidth * YProportion / XProportion;
    DisplayTop = (HeightAllowed - DisplayHeight) / 2;
  }

  // Set the image display size
  m_Lead1.SetDstRect(DisplayLeft, DisplayTop, DisplayWidth, DisplayHeight);
  m_Lead1.SetDstClipRect(DisplayLeft, DisplayTop, DisplayWidth, DisplayHeight);
  m_Lead1.ForceRepaint();
}

23. Add the code for the LEAD control to close the database when the application ends. To do so, open the ClassWizard (CTRL-W), and do the following:

a. In the Class name combo box, select CLtcdbView.

b. In the Object IDs list box, select CLtcdbView.

c. In the Messages list box, select WM_DESTROY.

d. Click the Add function button. Choose OK for the default function name (OnDestroy).

e. Click the Edit Code button to start entering the code.

f. Edit the function to appear as follows:

void CLtcdbView::OnDestroy() 
{
  CLtcdbView::OnDestroy();
  // TODO: Add your message handler code here
  m_Lead1.dbClose();
}

24. Add code to synchronize the LEAD control's record pointer with the DAO recordset's record pointer. To do so, add the following to the end of the CLtcdbView::DoDataExchange function:

if ((NormalMove == TRUE) && (NewPosition != m_pSet->GetAbsolutePosition()))
{
  OldPosition = NewPosition;
  NewPosition = m_pSet->GetAbsolutePosition();
  m_Lead1.dbMove(NewPosition - OldPosition);
}

25. In this step and the next one, change the resource file to add some buttons. For the first button, do the following:

a. Click on the ResourceView tab of the Project Workspace.

b. Double-click on the ltcdb resources folder.

c. Double-click on Dialog.

d. Double-click on IDD_LTCDB_FORM.

e. Select the command button control on the Controls toolbar.

f. Click and drag on the dialog window to create and position the button.

g. Double-click the button just created to bring up the Push Button Properties window.

h. Change the ID to IDC_ADDNEW and the caption to Add Record.

i. Close the window.

26. For the other buttons, repeat the preceding step with the following differences for each button:

- With an ID of IDC_FLIP and caption Flip Photo

- With an ID of IDC_DELETE and caption Delete Record

27. Add code for the IDC_ADDNEW button as follows:

a. Press Ctrl-W to go to the MFC Class Wizard.

b. In the Class name combo box, select CLtcdbView.

c. In the Object IDs list box, select IDC_ADDNEW.

d. In the Messages list box, select BN_CLICKED.

e. Click the Add function button. Choose OK for the default function name (OnAddnew).

f. Click the Edit Code button to start entering the code.

g. Edit the function to appear as follows:

void CLtcdbView::OnAddnew() 
{
  // Make sure we can add records to the database.
  if (!m_pSet->CanUpdate())
  {
    MessageBox("You cannot update this database.", "ERROR");
    return; 
  }

  // Get the name of the file to load.
  // This filter list is complete, except for GIF.
  CString ImageFilter = "Graphics|*.cmp; *.jpg; *.jff; *.jtf; *.bmp; *.tif; *.tga; *.pcx; *.cal; *.mac; *.mac; *.img; *.msp; *.wpg; *.wpg; *.ras; *.pct; *.pcd; *.eps; *.wmf||";
  CFileDialog  OpenFile(TRUE, NULL, NULL,0,ImageFilter);
  if (OpenFile.DoModal() != IDOK)
    return;
  CWaitCursor wait;  // Display an hourglass 
  CString MyFile = OpenFile.GetPathName();

  // Disable the normal synchronization code and hide the LEAD control
  // while we manipulate the recordsets.
  NormalMove = FALSE; 
  m_Lead1.ShowWindow (SW_HIDE);

  // Add a record to the DAO recordset.
  m_pSet->AddNew();
  SetDlgItemText(IDC_Cwho, MyFile);
  UpdateData();

try
  {
    m_pSet->Update();
  }
  catch( CDaoException* e )
  {
    AfxMessageBox( 
        e->m_pErrorInfo->m_strDescription, 
        MB_ICONEXCLAMATION );
    m_pSet->CancelUpdate();
    UpdateData(FALSE);
    m_Lead1.ShowWindow(SW_NORMAL);
    NormalMove = TRUE; 
    return; 
  }

  // Requery the DAO recordset and wait for the change to take effect.
  m_pSet->Requery();
  Sleep(5000);

  // Requery the LEAD recordset and make sure the recordsets match.
  m_Lead1.dbRequery();
  m_pSet->MoveFirst();
  NewPosition = 0;
  m_pSet->MoveLast();

  if (NewPosition != m_pSet->GetAbsolutePosition())
  {
    OldPosition = NewPosition;
    NewPosition = m_pSet->GetAbsolutePosition();
    m_Lead1.dbMove(NewPosition - OldPosition);
  }

  if(m_Lead1.GetDbCurrentRecord() != m_pSet->GetAbsolutePosition())
  {
    MessageBox("Synchronization error!\nDelete the record and exit", "ERROR", MB_OK);
    m_pSet->FindFirst(CString("who = '") + MyFile + CString("'"));
    return;
  }

  // Find the record we just added and synchronize the recordsets.
  m_pSet->FindFirst(CString("who = '") + MyFile + CString("'"));
  if (NewPosition != m_pSet->GetAbsolutePosition())
  {
    OldPosition = NewPosition;
    NewPosition = m_pSet->GetAbsolutePosition();
    m_Lead1.dbMove(NewPosition - OldPosition);
  }

  // Use return values to trap errors.
  BOOL ErrHand = m_Lead1.GetEnableMethodErrors(); // Save the current setting.
  m_Lead1.SetEnableMethodErrors(FALSE);

  // Redisplay the LEAD control, load the image, update the record,
  // and restore normal recordset synchronization.
  m_Lead1.ShowWindow(SW_NORMAL);
  m_Lead1.dbEdit();
  int nRet = m_Lead1.Load(MyFile, 24, 0, 1);
  if (nRet != 0)
  {
    char  buffer[50];
    sprintf(buffer, "LEAD Error %d loading image.", nRet);
    MessageBox(buffer, "ERROR", MB_OK);
  }

  // Update the recordset using the appropriate format
  if (m_Lead1.GetBitmapBits() == 1)
     nRet = m_Lead1.dbUpdate(FILE_LEAD1BIT, 1, 0);
  else if  (m_Lead1.GetBitmapBits() == 4)
     nRet = m_Lead1.dbUpdate(FILE_PCX, 4, 0);
  else if (m_Lead1.GetIsGrayscale() == GRAY_NO)
     nRet = m_Lead1.dbUpdate(FILE_CMP, 24, QFACTOR_QMS);
  else // save as grayscale
     nRet = m_Lead1.dbUpdate(FILE_CMP, 8, QFACTOR_QMS);
  if (nRet != 0)
  {
    char  buffer[50];
    sprintf(buffer, "LEAD Error %d saving image to database", nRet);
    MessageBox(buffer, "ERROR", MB_OK);
  }
  // Restore previous error handling.
  m_Lead1.SetEnableMethodErrors(ErrHand);
  NormalMove = TRUE;

}

28. Add code for the IDC_FLIP button as follows:

a. Press Ctrl-W to go to the MFC Class Wizard.

b. In the Class name combo box, select CLtcdbView.

c. In the Object IDs list box, select IDC_FLIP.

d. In the Messages list box, select BN_CLICKED.

e. Click the Add function button. Choose OK for the default function name (OnUpdate).

f. Click the Edit Code button to start entering the code.

g. Edit the function to appear as follows:

void CLtcdbView::OnFlip() 
{
  // Make sure we can update records in the database.
  if (m_Lead1.GetDbCanUpdate() == FALSE)
  {
    MessageBox("You cannot update this database.", "ERROR");
    return; 
  }

  // Make sure the current record contains an image.
  if (m_Lead1.GetDbIsEOF() || m_Lead1.GetDbIsBOF() || m_Lead1.GetBitmap() == 0)
  {
    MessageBox("No current image", "NOTICE", MB_OK);
    return;
  }

  // Flip the image.
  m_Lead1.dbEdit();
  m_Lead1.Flip();

  // Use return values to trap errors.
  BOOL ErrHand = m_Lead1.GetEnableMethodErrors(); // Save the current setting.
  m_Lead1.SetEnableMethodErrors(FALSE);

  // Update the recordset using the appropriate format

   int nRet;
  if (m_Lead1.GetBitmapBits() == 1)
     nRet = m_Lead1.dbUpdate(FILE_LEAD1BIT, 1, 0);
  else if  (m_Lead1.GetBitmapBits() == 4)
     nRet = m_Lead1.dbUpdate(FILE_PCX, 4, 0);
  else if (m_Lead1.GetIsGrayscale() == GRAY_NO)
     nRet = m_Lead1.dbUpdate(FILE_CMP, 24, QFACTOR_QMS);
  else // save as grayscale
     nRet = m_Lead1.dbUpdate(FILE_CMP, 8, QFACTOR_QMS);
  if (nRet != 0)
  {
    char  buffer[50];
    sprintf(buffer, "LEAD Error %d saving image to database", nRet);
    MessageBox(buffer, "ERROR", MB_OK);
  }

  // Restore previous error handling.
  m_Lead1.SetEnableMethodErrors(ErrHand);
}

29. Add code for the IDC_DELETE button as follows:

a. Press Ctrl-W to go to the MFC Class Wizard.

b. In the Class name combo box, select CLtcdbView.

c. In the Object IDs list box, select IDC_DELETE.

d. In the Messages list box, select BN_CLICKED.

e. Click the Add function button. Choose OK for the default function name (OnDelete).

f. Click the Edit Code button to start entering the code.

g. Edit the function to appear as follows:

void CLtcdbView::OnDelete() 
{
  // Make sure we can delete records from the database.
  if (!m_pSet->CanUpdate())
  {
    MessageBox("You cannot update this database.", "ERROR");
    return; 
  }

  // Make sure the database is not empty.
  if (m_pSet->IsEOF() && m_pSet->IsBOF())
  {
    MessageBox("The database is empty", "NOTICE", MB_OK);
    return;
  }
  CWaitCursor wait;  // Display an hourglass 

  // Disable the normal record synchronization and hide the LEAD control.
  NormalMove = FALSE;
  m_Lead1.ShowWindow(SW_HIDE);

  // Save the current position.
  long RecMarker = m_pSet->GetAbsolutePosition();

  // Delete the current record and wait for the change to take effect.
  m_pSet->Delete();
  Sleep(5000);

  // Requery the recordsets, move to the last record,
  // and initialize the NewPosition global variable.
  m_pSet->Requery();
  m_Lead1.dbRequery();
  m_pSet->MoveLast();
  NewPosition = m_pSet->GetAbsolutePosition();
  m_Lead1.dbMove(NewPosition);

  // Make sure the LEAD control is on the last record.
  m_Lead1.dbMoveNext();
  if(m_Lead1.GetDbIsEOF())
  {
    m_Lead1.dbMovePrev();
  }
  else
  {
    int nRet = MessageBox("Synchronization error!\nRestart the application", "ERROR", MB_OK);
    return;
  }

  // Return to the old record position, if possible.
  NormalMove = TRUE;
  m_Lead1.ShowWindow(SW_NORMAL);
  long RelativePosition = RecMarker - NewPosition;
  if (RelativePosition < 0)
  {
    m_pSet->Move(RelativePosition);
  }

  // Update the edit box with data from the current record.
  UpdateData(FALSE);

}

30. To accommodate a 256-color display driver, continue with the remaining steps, which send WM_QUERYNEWPALETTE and WM_PALETTECHANGED messages to the LEADTOOLS ActiveX.

31. Edit the LTCDBVIEW.H file and insert the following lines in the definition of CLtcdbView class, just before DECLARE_MESSAGE_MAP():

afx_msg BOOL OnQueryNewPalette();
afx_msg void OnPaletteChanged( CWnd *pwin );

32. Edit the LTCDBVIEW.CPP file and insert the following lines between the lines: BEGIN_MESSAGE_MAP(CLtcdbView, CDaoRecordView) and END_MESSAGE_MAP():

ON_WM_QUERYNEWPALETTE()
ON_WM_PALETTECHANGED()

33. Go to the end of LTCDBVIEW.CPP file and insert the following two functions:

BOOL CLtcdbView::OnQueryNewPalette()
{
  if(!IsWindow(m_Lead1.m_hWnd))
      return FALSE;
  return m_Lead1.SendMessage(WM_QUERYNEWPALETTE);
}

void CLtcdbView::OnPaletteChanged(CWnd* pFocusWnd)
{
  if(pFocusWnd->m_hWnd == m_hWnd || !IsWindow(m_Lead1.m_hWnd))
      return;
  m_Lead1.SendMessage(WM_PALETTECHANGED, (WPARAM) pFocusWnd->m_hWnd);
}

34. On the main menu, select Build > Build ltcdb.exe to build the project.

35. On the main menu, select Build > Execute ltcdb.exe to run the project.