• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 298
  • Last Modified:

Performing multi-threaded listbox updates

I have an owner-drawn listbox that displays bitmaps that are retrieved from the web after the box has been displayed.  I display a default image until the image has been downloaded, then replace the "blank" with the real image and start downloading the next image.  (This is quite similar in execution to a standard web browser.)

This function starts the retrieval thread, then is called by the thread when the thread is complete and starts a new thread to retrieve the next image:

========================================
long CMyDlg::RetrieveImage(WPARAM vResult, LPARAM l)
{

      THREADPARAMS* pParams = new THREADPARAMS;
      pParams->vImageToRetrieve = m_ImageToRetrieveCounter;
      pParams->pImageList = &(m_pParent->m_pCurrentProc->m_OnlineImageList);
      pParams->pListBox = &m_cThumbListBox;
      pParams->hListBox = m_cThumbListBox.m_hWnd;
      pParams->pWeb = &(m_pParent->m_HospitalWeb);
      pParams->pWnd = this;

      // Are there more images to retrieve?  If not, just stop.
      if (m_ImageToRetrieveCounter >= m_TotalImages)
            return 0;

      // Activate the acquisition thread
      m_pThread = AfxBeginThread(ActivateAcquisitionThread,
            pParams);

      // Increment the counter for the next call
      m_ImageToRetrieveCounter++;

      return 1;
}
============================

This next function is the thread function.  It retrieves the image and puts it into the list box.

=========================================
UINT CMyDlg::ActivateAcquisitionThread(LPVOID pParam)
{
      CJpegDecoder vDecoder; // Will decode the retrieved jpeg
      CImageFunctions vIFunc;
      CBitmap *pBM;
      HBITMAP hThumbnail;
      CPoint vImageDim;
      BYTE *vImage;
      CThumbListBox *pListBox;
      CWnd *pWnd;
      THREADPARAMS *pParams = new THREADPARAMS;

      pParams = (THREADPARAMS *)pParam;
      CStringList *pList = pParams->pImageList;

      // Retrieve the name of the image
      CString vImageName = pList->GetAt(pList->FindIndex(
            pParams->vImageToRetrieve));

      pListBox = (CThumbListBox *)CWnd::FromHandle((HWND)pParams->hListBox);

      // Retrieve the image, and get the saved file name
      vImageName = pParams->pWeb->GetImage(vImageName);
      if (vImageName == "")
      {
            pParams->pWnd->PostMessage(UWM_THREAD_COMPLETE, -1);
            return -1;
      }

      // Decode the image
      if ((vImage = vDecoder.Decode(vImageName, vImageDim)) == NULL)
      {
            pWnd->PostMessage(UWM_THREAD_COMPLETE, -2);
            return -2;
      }

        // Calls a func that assembles a BITMAP
      if (vDecoder.GetColorSpace() == JCS_GRAYSCALE)
            // Convert to RGB
            hThumbnail = vIFunc.MakeBitmap(vImage, vImageDim, 1, pParams->pWnd);
      else hThumbnail = vIFunc.MakeBitmap(vImage, vImageDim, 3, pParams->pWnd);

      // Make the bitmap and attach it
      pBM = new CBitmap();
      pBM->Attach((HBITMAP)hThumbnail);

      // Add to the list box
      pListBox->AddThumbnail(pBM);

      // Post a "Completed" message
      pParams->pWnd->PostMessage(UWM_THREAD_COMPLETE, 1);

      return 1;
}
==================================

If the operations are performed in the main thread, they work fine, but I want to display the list box while the images are loading.  I keep getting errors, though.  With this code, it has trouble deleting the HWND.  The errors seem to vary somewhat, so I assume it has more to do with modifying memory elements that I shouldn't be modifying.  

Does anyone have suggestions on how I can approach this problem more safely?  If there is an alternative that is completely different from what I have shown, please share!

Thanks.  (PS if this question is harder than I think I'll up  the points.  Since every browser does this, though, it seems pretty simple.)
0
DenMan
Asked:
DenMan
  • 3
  • 2
1 Solution
 
prasanthCommented:
Hi,

Couple of things:
1) Most MFC objects are confined to a single thread. You cannot pass CWnds and other such classes between threads. You can, however, pass around the underlying Windows handle, HWND in the case of CWnds. Then in the "receiving" thread use a CWnd::FromHandle(). You are doing this already with pParams->hListBox, but it looks like you are also passing pParams->pListBox which is basically useless.

2) Instead of pParams->pWnd, pass the underlying HWND as pParams->hWnd or whatever. In ActivateAcquisitionThread you have a CWnd* pWnd but it doesn't seem to have been initialized. Use

CWnd* pWnd = CWnd::FromHandle(pParams->hWnd)

instead. Also, right now

pWnd->PostMessage(UWM_THREAD_COMPLETE, -2);

should be failing because pWnd has not been initialized to anything.

3) In ActivateAcquisitionThread, you have

THREADPARAMS *pParams = new THREADPARAMS;
pParams = (THREADPARAMS *)pParam;

But all you really need is

THREADPARAMS *pParams = (THREADPARAMS *)pParam;

because the structure has already been allocated; no need to allocate another one and then lose track of it. Also, be sure to free pParams before ActivateAcquisitionThread exits otherwise you'll find yourself with a memory leak.

4) You may or may not need a lock on pParams->pImageList, depending on if it can be changed while any of the ActivateAcquisitionThread threads are running. If the list is simply created once and then all the worker threads are run, you don't need to lock it.

5) I don't know what kind of an object pParams->pWeb is, but it too might need a lock if there is a possibility that is will be changed while one of the ActivateAcquisitionThreads is running.

Hope this helps...
0
 
DenManAuthor Commented:
Those comments were quite helpful.

pWeb is an object derived from CWnd that includes a CSocket and a variety of variables (such as site name, URL, etc) that relate to interacting with a couple web pages.  I have noticed that passing the HWND allows me to call the functions within the pWeb object, but all the variables are unassigned.  Is there any way to pass the pWeb object to the new thread yet still have access to the previously-assigned member variables?
0
 
prasanthCommented:
One way to do this is to put all the info (socket handles, session variables, and what-not) into a separate CWebInfo (or something) class. Then in pWeb's class definition, include a CWebInfo as a member variable: (for example)

class CHospitalWeb : public CWnd
{
public:
    CWebInfo m_info;

    CHospitalWeb();
    ~CHospitalWeb();
    etc.
    etc.
}

CWebInfo really doesn't even need to be a class. It can be a simply struct:

typedef struct
{
   CSocket m_socket;
   CString m_username;
   CString m_password;
   etc.
   etc.
}  CWebInfo;

And regardless of whether it is a class or a struct, you can pass CWebInfo*'s between threads because they are not derived from CWnd. But again, you may need to put a lock on it if there is a possibility CWebInfo or any of it's member fields might be changed while another thread is reading it.

Hope this helps.
0
 
DenManAuthor Commented:
Hmmm...I'm still having trouble.  I can pass the CWebInfo * just fine.  It seems that I need to pass the HWND for the CHospitalWeb AND the CWebInfo *.  Then, since the m_Info within pWeb is now unassigned, I need to set those values equal to the CWebInfo *.  The problem is that if I try to set any of pWeb's variables, I get a null exception.  Will locking the CHospitalWeb solve this problem, or is there something else I'm forgetting?
0
 
prasanthCommented:
you need to construct a new CWebInfo before you assign it's variables...
0

Featured Post

[Webinar] Improve your customer journey

A positive customer journey is important in attracting and retaining business. To improve this experience, you can use Google Maps APIs to increase checkout conversions, boost user engagement, and optimize order fulfillment. Learn how in this webinar presented by Dito.

  • 3
  • 2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now