Solved

CListBox scroll problem

Posted on 2003-11-14
33
1,355 Views
Last Modified: 2013-11-20
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.
0
Comment
Question by:firefox2003
  • 13
  • 12
  • 4
  • +1
33 Comments
 
LVL 11

Expert Comment

by:KurtVon
ID: 9748000
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
 
LVL 13

Expert Comment

by:SteH
ID: 9748045
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
 
LVL 44

Expert Comment

by:AndyAinscow
ID: 9748069
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
 

Author Comment

by:firefox2003
ID: 9748180
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
 
LVL 11

Expert Comment

by:KurtVon
ID: 9748283
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
 
LVL 44

Expert Comment

by:AndyAinscow
ID: 9748405
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
 

Author Comment

by:firefox2003
ID: 9748406
KurtVon that sounds pretty much what i want to do.

cheers

=)
0
 
LVL 11

Expert Comment

by:KurtVon
ID: 9748693
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
 

Author Comment

by:firefox2003
ID: 9748978
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
 
LVL 11

Expert Comment

by:KurtVon
ID: 9749193
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
 

Author Comment

by:firefox2003
ID: 9749307
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
 
LVL 11

Expert Comment

by:KurtVon
ID: 9749722
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
 

Author Comment

by:firefox2003
ID: 9749762
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
 
LVL 11

Expert Comment

by:KurtVon
ID: 9749862
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
 

Author Comment

by:firefox2003
ID: 9750421
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
Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

 
LVL 11

Expert Comment

by:KurtVon
ID: 9750507
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
 

Author Comment

by:firefox2003
ID: 9760098
owner draw has been set and when you select an item it just gets selected nothing else happens.

*and still trying
0
 
LVL 44

Expert Comment

by:AndyAinscow
ID: 9762101
'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
 

Author Comment

by:firefox2003
ID: 9762567
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
 
LVL 44

Expert Comment

by:AndyAinscow
ID: 9763001
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
 

Author Comment

by:firefox2003
ID: 9763383
yes i know that, thats what im trying to do. *sigh*
0
 
LVL 11

Expert Comment

by:KurtVon
ID: 9763750
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
 

Author Comment

by:firefox2003
ID: 9765001

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

??????

tomski
0
 
LVL 11

Expert Comment

by:KurtVon
ID: 9765170
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
 

Author Comment

by:firefox2003
ID: 9765411
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
 
LVL 11

Expert Comment

by:KurtVon
ID: 9765496
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
 

Author Comment

by:firefox2003
ID: 9765776
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
 
LVL 11

Accepted Solution

by:
KurtVon earned 50 total points
ID: 9765976
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
 

Author Comment

by:firefox2003
ID: 9767705
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
 
LVL 11

Expert Comment

by:KurtVon
ID: 9771089
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

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

Introduction: Ownerdraw of the grid button.  A singleton class implentation and usage. Continuing from the fifth article about sudoku.   Open the project in visual studio. Go to the class view – CGridButton should be visible as a class.  R…
Introduction: Dialogs (2) modeless dialog and a worker thread.  Handling data shared between threads.  Recursive functions. Continuing from the tenth article about sudoku.   Last article we worked with a modal dialog to help maintain informat…
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.
When you create an app prototype with Adobe XD, you can insert system screens -- sharing or Control Center, for example -- with just a few clicks. This video shows you how. You can take the full course on Experts Exchange at http://bit.ly/XDcourse.

747 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

9 Experts available now in Live!

Get 1:1 Help Now