CListBox scroll problem

Hi,

I have a listbox in a dialog called m_ctlListBox, when i add strings to it m_ctlListBox.AddString(message);
that are longer than the width of the box it dissapears off the end, how can I stop this happening?

also when the listbox becomes full it scrolls but it does not auto scroll down, is there a way to do this?

i have tried using an edit box which solves the text overflowing the width as it word wraps but the same problem with the box not scrolling down to show to most latest addtion to the box.

Help me =(

thanks in advance.
firefox2003Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

KurtVonCommented:
The only way to keep the text from going off the right edge of the listbox is to use the GetTextExtents function to see if it fits and then shorten the string if it doesn't.  A better solution might be to set the WS_HSCROLL style.  You could also draw the text yorself by using owner draw with variable hight.

To make a list box scroll down, just use SetTopIndex.  The easy way is something like

    m_cListBox->SetTopIndex(m_cListBox->AddString(strToAdd));

A more sophisticated method figures out where (in a sorted list) the new string is and decides whether to scroll up or down to display it.

Hope this helps.
0
SteHCommented:
For the down scrolling I have an easy answer:
int ind = m_ctlListBox.AddString (message);
m_ctlListBox.SetTopIndex (ind);
tries to put the new string at the first pos in the list. If there are not enough elements afterwards, it just scrolls down to make that element visible.

For the horizontal what would you like to have? A horizontal scroll bar or a line wrap? A horizontal scroll bar can be added by popping up the properties of the ctrl. ON the styles tab check the Horizontal scroll. A line wrap changes the number of entries and might be difficult to treat. And there I think it is best to see the beginning (no automatic scrolling in this direction).
0
AndyAinscowFreelance programmer / ConsultantCommented:
For a line wrap it would require an ownerdraw list box with lines of variable height.
You could however provide a tooltip which displays the complete line of text when the mouse is over a line in your list box.
0
Cloud Class® Course: Ruby Fundamentals

This course will introduce you to Ruby, as well as teach you about classes, methods, variables, data structures, loops, enumerable methods, and finishing touches.

firefox2003Author Commented:
Hi,

both of those worked:

m_cListBox->SetTopIndex(m_cListBox->AddString(strToAdd));

and

int ind = m_ctlListBox.AddString (message);
m_ctlListBox.SetTopIndex (ind);

excellent thats vertical scrolling sorted cheers,

For the horizontal i would like to line wrap so the message if exceeds the width would carry onto a new line.

my solution i was trying adapt was read in the length of the message if it exceeds 37 (the width of my listbox)
cut up the message but, with this i came across a problem, capitals took up more space than lower case
so it began to look odd.

unless you know a better way to solve this.

cheers
0
KurtVonCommented:
You can use GetTextExtents to find the actual size of the text, then chop it at the appropriate width based on the actual width of the control.

I actually have some code somewhere aropund here for doing it, but it may not be accessible to me at the moment.  I used an owner-draw variable hight list box and made sure to also alternate colors for rows in the listbox so that it was clear which lines belonged to which.  You could also try using creative indenting.

I'll see what I can locate.
0
AndyAinscowFreelance programmer / ConsultantCommented:
If you cuat and add two lines then each is an individual line.  You would be better with ownerdraw so that it is still one line.
0
firefox2003Author Commented:
KurtVon that sounds pretty much what i want to do.

cheers

=)
0
KurtVonCommented:
Couldn't find the code, I think it's at home.  Here's a quick-and-dirty version I just slapped together, sorry the code looks so sloppy.  It should do what you want but you will probably need to tweak it to get something that looks nice:

void CTestDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{

    if (nIDCtl == IDC_LIST1)
    {
        CListBox* pList = (CListBox*)GetDlgItem(IDC_LIST1);
        CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
        CString strText, strTest;
        int nLineStart;
        CSize szText;
        CRect rcText;

        strText = GetTextForItem(lpDrawItemStruct->itemID);
        rcText = lpDrawItemStruct->rcItem;

        if (lpDrawItemStruct->itemState & ODS_SELECTED)
        {
            pDC->SetBkColor(GetSysColor(COLOR_HIGHLIGHT));
            pDC->SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
        }
        else if ((lpDrawItemStruct->itemID % 2) == 0)
            pDC->SetBkColor(RGB(255, 255, 255));
        else
            pDC->SetBkColor(RGB(128, 255, 128));

        nLineStart = 0;
        strText.TrimRight();
        while (nLineStart < strText.GetLength())
        {
            strTest = strText.Mid(nLineStart);
            szText = pDC->GetTextExtent(strTest);
            while (szText.cx > rcText.Width())
            {
                // Modify this to trim to the next breaking
                // character if you want it to look nicer.
                strTest = strTest.Left(strTest.GetLength() - 1);
                szText = pDC->GetTextExtent(strTest);
            }

            rcText.bottom = rcText.top + szText.cy;
            pDC->ExtTextOut(rcText.left, rcText.top, ETO_OPAQUE, rcText, strTest, NULL);
            rcText.top = rcText.bottom;
       
            nLineStart += strTest.GetLength();
            while ((nLineStart < strText.GetLength()) && (strText.GetAt(nLineStart) == ' '))
                nLineStart++;
        }
       
    }
    else
        CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
}
0
firefox2003Author Commented:
strText = GetTextForItem(lpDrawItemStruct->itemID); <--- this line whats GetTextForItem ???

I think i see what it is doing,

with my listbox i have set owerdraw, but to what does it need to be set as? no, fixed, variable ?

cheers
0
KurtVonCommented:
GetTextForItem is just a function I wrote that returns the text for the items.  With owner draw list boxes you need to keep track of the strings yourself.  The itemID is the index in the listbox.

Set the list box to Owner Draw Fixed if every row should have the same number of lines (say 2 lines, or three) and Owner Draw variable if the entries have differing numbers of lines.  Either way, you will need to catch the WM_MEASUREITEM message to let the list box know how tall the items are.  It gets a little complicated with variable lines, since you have to computer (or at least estimate) how many lines there will be for each item.
0
firefox2003Author Commented:
could you post the code for -> GetTextForItem is just a function I wrote that returns the text for the items.

and will have a play then with what i can get to work.

thanks
0
KurtVonCommented:
CString& CTestDialog::GetTextForItem(int)
{
    static CString strText;
    if (strText.IsEmpty())
        strText = "Here is a long and pointless string just to be wrapped";

    return strText;
}

I'm afraid it isn't really useful. :-)

Usually I just keep a CStringArray of the strings when I do something like this, or generate the strings on the fly from other data.  It depends on the situation.
0
firefox2003Author Commented:
ok so how do i call this as before i have been doing this:

void CMessengerDlg::doProcessMsg(char *token)
{
      char seps[] = " ";

      token = strtok( NULL, seps ); //takes off msg
      CString buddy = token; // leaving nick
      token = strtok( NULL, "" ); //takes off nick
      CString msg = "    ";
      msg += token; // for the first non loop

      //add the message to the received box
      m_sRecvd.AddString(buddy); // the buddy
      m_sRecvd.SetTopIndex(m_sRecvd.AddString(msg)); //the message

      m_sMsgRecvd = msg; // add message to global variable to play with

}

i have added your two functions, but entirely sure what to do next.

thanks
0
KurtVonCommented:
Add a member to CMessengerDlg

CStringArray m_astrItems;

then

void CMessengerDlg::doProcessMsg(char *token)
{
     char seps[] = " ";

     token = strtok( NULL, seps ); //takes off msg
     CString buddy = token; // leaving nick
     token = strtok( NULL, "" ); //takes off nick
     CString msg = "    ";
     msg += token; // for the first non loop

     //add the message to the received box
     m_astrItems.Add(buddy); // the buddy
     m_sRecvd.AddString("dummy item");
     m_astrItems.Add(m_sRecvd.AddString(msg));
     m_sRecvd.SetTopIndex(m_sRecvd.AddString("dummy message")); //the message

     m_sMsgRecvd = msg; // add message to global variable to play with
}

CString& CMessengerDlg::GetTextForItem(int index)
{
    return m_astrItems.GetAt(index);
}

0
firefox2003Author Commented:
have given that a go but still it does not word/line wrap/

ideas?

it still displays the message but continues instead of it being on a new line.
0
KurtVonCommented:
Sounds like owner draw wasn't set or the lines are wrapping, but the item height is being set incorrectly.  What happens when you select an item that should be word-wrapped?
0
firefox2003Author Commented:
owner draw has been set and when you select an item it just gets selected nothing else happens.

*and still trying
0
AndyAinscowFreelance programmer / ConsultantCommented:
'when you select an item it just gets selected nothing else happens'

What else should happen to a line you select apart from it being selected that is?
0
firefox2003Author Commented:
hi,

was reply to -> What happens when you select an item that should be word-wrapped?

as nothing happens when you select an item,

why is this not word wrapping?

0
AndyAinscowFreelance programmer / ConsultantCommented:
No.
Word wrapping is splitting a line onto two or more lines
eg

This is a long long long long long line of text which is to be word wrapped

going to
This is a long long long long long line
of text which is to be word wrapped

to make it fit.
0
firefox2003Author Commented:
yes i know that, thats what im trying to do. *sigh*
0
KurtVonCommented:
What does your OnMeasureItem do?  Just as a test, try making the code something like

void CMessengerDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    if (nIDCtl == IDC_LIST1)
    {
        lpMeasureItemStruct->itemHeight = 30;
    }
    else
        CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}

That should force the items to all be large enough for two lines of text.

If that doesn't work, try putting a breakpoint in the owner draw code, just to make sure it is called.  I may have thrown the code together, but I did compile and test it to make sure it worked, and had no problems.

0
firefox2003Author Commented:

Is there another way to do this as this just does not seem to be working.

??????

tomski
0
KurtVonCommented:
Not that I know of.

Let's go over this piecwise.  I assume from you comment OnOwnerDraw is not being called.  Did you put the breakpoint in the "if" test or outside it?  If it breaks outside, then perhaps the control ID is incorrect.  If it never breaks, double check that the control is owner draw and that the dialog is the direct owner of the control (this is especially important if everything is on tab pages).

You can also double check that a message is being sent with Spy++.
0
firefox2003Author Commented:
well this is what i have with out the code to get it to word wrap.

http://212.158.208.253/code1.rar

to see if i have done this bit right first.

tomski
0
KurtVonCommented:
Aside from the owner draw attribute, I don't see any owner draw handling code in the program you posted.  You need to handle the messages WM_DRAWITEM and WM_MEASUREITEM in CMessengerDlg.

Did you post the wrong code, or have you removed the handlers?
0
firefox2003Author Commented:
just wanted to make sure that it was ok but posted wrong directory, this is what i am using:

http://212.158.208.253/code1.rar <-- second try *sorry*

tomski
0
KurtVonCommented:
LBS_OWNERDRAWVARIABLE is not set in the dialog resource.  If you are using VS6.0 you need to go to the second page and choose "Variable height" in the Owner Draw combo box.  If you are using VS.NET you need to choose "Variable" in teh Owner Draw property grid item.

That shoudl solve the problem.

BTW, here's a quick way to compute the actual height of the items.  I just threw it together from the existing code:

void CMessagingDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    if (nIDCtl == IDC_LIST1)
    {
        int nLineStart;
        CRect rcClient;
        CSize szText;
        CString strText, strTest;
        CListBox* pList = (CListBox*)GetDlgItem(IDC_LIST1);
        CDC* pDC = pList->GetDC();
          CFont* pFont = pDC->SelectObject(GetFont());
       
        pList->GetClientRect(rcClient);

        strText = GetTextForItem(lpMeasureItemStruct->itemID);
        lpMeasureItemStruct->itemHeight = 0;

        nLineStart = 0;
        strText.TrimRight();
        while (nLineStart < strText.GetLength())
        {
            strTest = strText.Mid(nLineStart);
            szText = pDC->GetTextExtent(strTest);
            while (szText.cx > rcClient.Width())
            {
                // Modify this to trim to the next breaking
                // character if you want it to look nicer.
                strTest = strTest.Left(strTest.GetLength() - 1);
                szText = pDC->GetTextExtent(strTest);
            }
            lpMeasureItemStruct->itemHeight += szText.cy;

            nLineStart += strTest.GetLength();
            while ((nLineStart < strText.GetLength()) && (strText.GetAt(nLineStart) == ' '))
                nLineStart++;
        }

        pDC->SelectObject (pFont);
        pList->ReleaseDC(pDC);
    }
    else
        CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
firefox2003Author Commented:
Hi,

I have made those changes but with variable height option set it causes the app to crash, if i leave it off well it does not wrap.

error:

      { ASSERT(nIndex >= 0 && nIndex < m_nSize);

stack:

CStringArray::GetAt(int 0) line 222 + 55 bytes
CMessengerDlg::GetTextForItem(int 0) line 513 + 19 bytes
CMessengerDlg::OnMeasureItem(int 1005, tagMEASUREITEMSTRUCT * 0x0012efb0) line 530 + 15 bytes
CWnd::OnWndMsg(unsigned int 44, unsigned int 1005, long 1241008, long * 0x0012ede0) line 1930
CWnd::WindowProc(unsigned int 44, unsigned int 1005, long 1241008) line 1585 + 30 bytes
AfxCallWndProc(CWnd * 0x0012fd60 {CMessengerDlg hWnd=???}, HWND__ * 0x00040446, unsigned int 44, unsigned int 1005, long 1241008) line 215 + 26 bytes
AfxWndProc(HWND__ * 0x00040446, unsigned int 44, unsigned int 1005, long 1241008) line 368

      m_nSize      0
      nIndex      0
-      this      0x0012fdbc {CStringArray}
+      CObject      {CObject}
-      classCStringArray      {"CStringArray"}
+      m_lpszClassName      0x00538c60 "CStringArray"
      m_nObjectSize      20
      m_wSchema      0
      m_pfnCreateObject      0x004c0c20 CStringArray::CreateObject(void)
+      m_pBaseClass      0x005262a8 struct CRuntimeClass const CObject::classCObject
+      m_pNextClass      0x00000000 {???}
-      m_pData      0x00000000 {???}
      m_pchData      CXX0030: Error: expression cannot be evaluated
      m_nSize      0
      m_nMaxSize      0
      m_nGrowBy      0


if any of that makes sense?

0
KurtVonCommented:
Yup, it means the string array has less entries than the list box.  Looking at the code you sent it looks like strings are being added to the list box without being added to the string array in OnBsend.  You will need to replace the two places this is done with the same sort of thing as in doProcessMessage.

0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
System Programming

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.