Solved

Performing multi-threaded listbox updates

Posted on 1998-08-20
5
279 Views
Last Modified: 2013-11-20
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
Comment
Question by:DenMan
  • 3
  • 2
5 Comments
 
LVL 2

Accepted Solution

by:
prasanth earned 50 total points
Comment Utility
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
 

Author Comment

by:DenMan
Comment Utility
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
 
LVL 2

Expert Comment

by:prasanth
Comment Utility
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
 

Author Comment

by:DenMan
Comment Utility
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
 
LVL 2

Expert Comment

by:prasanth
Comment Utility
you need to construct a new CWebInfo before you assign it's variables...
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

Introduction: Dynamic window placements and drawing on a form, simple usage of windows registry as a storage place for information. Continuing from the first article about sudoku.  There we have designed the application and put a lot of user int…
Introduction: Finishing the grid – keyboard support for arrow keys to manoeuvre, entering the numbers.  The PreTranslateMessage function is to be used to intercept and respond to keyboard events. Continuing from the fourth article about sudoku. …
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now