|
Hi,
I added two functions which I missed:
int CSortListCtrl::InsertColumn(int nCol, LPCTSTR lpszColumnHeading, int Format, int nWidth, int nSubItem)
{
if (nCol >= m_iNumColumns)
m_iNumColumns = nCol+1;
return CListCtrl::InsertColumn(nCol, lpszColumnHeading, Format, nWidth, nSubItem);
}
int CSortListCtrl::InsertItem( int index, LPCTSTR pszText, int image)
{
const int iIndex = CListCtrl::InsertItem( index, pszText, image );
LPTSTR* arrpsz = new LPTSTR[ m_iNumColumns ];
arrpsz[ 0 ] = new TCHAR[ lstrlen( pszText ) + 1 ];
(void)lstrcpy( arrpsz[ 0 ], pszText );
for (int i = 1; i < m_iNumColumns; ++i)
arrpsz[ i ] = 0;
VERIFY( SetTextArray( iIndex, arrpsz ) );
return iIndex;
}
class CSortListCtrl : public CListCtrl
{
...
int InsertColumn(int nCol, LPCTSTR lpszColumnHeading, int Format = LVCFMT_LEFT, int nWidth = -1, int nSubItem = -1);
int InsertItem( int index, LPCTSTR pszText, int = 0);
...
};
It's now a bit more compatible with the standard CListCtrl.
Sincerely
Volkmar Kostka
|
|
|
|
|
Thank you sir!
I was trying to sort out a problem I was having with migrating from the standard ListCtrl to this code and inserting your functions made for a quick fix. I appreciate it!
|
|
|
|
|
Hi!
Nice initiative.
However addings these two functions to the Extended list control causes the application to crash at startup.
I didn't yet started to figure out what's the problem.
If anybody knows, please post a message here.
Best wishes!
I am in love with VC++
|
|
|
|
|
This is because AddItem is implemented in terms of InsertItem, both of which are calling SetTextArray(), the second call of which ASSERTs because there is item data.
The simplest solution is to change the call to InsertItem in AddItem to CListCtrl::InsertItem
--
http://www.coruscant.ltd.uk/
|
|
|
|
|
In the CSortHeaderCtrl::SetSortArrow() method, there is a bug
// for every column, clear the sort up/down flags
for ( int i=0; i < GetItemCount(); i++ )
{
HD_ITEM hditem;
GetItem(i, &hditem);
hditem.mask = HDI_FORMAT;
hditem.mask = HDI_FORMAT needs to be set before the call to GetItem
ie the above snippet should read:
// for every column, clear the sort up/down flags
for ( int i=0; i < GetItemCount(); i++ )
{
HD_ITEM hditem;
hditem.mask = HDI_FORMAT;
GetItem(i, &hditem);
|
|
|
|
|
Hi Mark!
I guess you are not aware of CListCtrl::FindItem function.
in your article you said (you wrote "real" in CAPS )
Implementation
It was a REAL pain to do, your callback function gets the item data for the two rows to compare, but what use is that, you need the text to compare! This control stores the text for the columns in the item data, so the compare function can get at it, it also allows users of the control to use the item data as usual.
You can always find index of item by providing item data and use FindItem function and once you have index, you can get everything about that item.
Here is what I do inside a list control sorting callback function.
<br />
int <br />
CMyListCtrl::CompareProc(LPARAM lParam1, LPARAM lParam2)<br />
{<br />
int nRet = 0;<br />
LVFINDINFO fi = { 0 };<br />
fi.flags = LVFI_PARAM;<br />
fi.lParam = lParam1;<br />
int item1 = FindItem(&fi);<br />
<br />
fi.lParam = lParam2;<br />
int item2 = FindItem(&fi);<br />
CString csTextItem1 = GetItemText(item1, mnSortCol);<br />
CString csTextItem2 = GetItemText(item2, mnSortCol); <br />
nRet = _tcsicmp((LPCTSTR)csTextItem1, (LPCTSTR)csTextItem2);<br />
return mbSortAscending ? nRet : -nRet; <br />
}<br />
Note that mnSortColumn and mbSortAscending are class members, mnSortColumn represents the column on which user is sorting and mbSortAscending represents if we are sorting in ascending or descending order.
There is a drawback to my approach too, item data for every single item need to be unique, if they are not then this function will not sort correctly, I personally never had a situation where I had to provide same item data for multiple items in the list control, but you never know.
Furthermore, if the programmer completely forgets to provide item data for inserted items, this approach will not work at all, so providing unique item data for items is a pre-requisite for this callback to work.
Thanks for sharing your effort with others though, it is appreciable.
/yawar
I have no signature!
|
|
|
|
|
Yawar Maajed wrote:
You can always find index of item by providing item data and use FindItem function and once you have index, you can get everything about that item.
Reading your subject I thought you were about to give an even better way of getting the index. Theres no need to use FindItem (relatively inefficient with large lists), or store a unique / sort value in item data (limiting).
Instead, you simply need to use LVM_SORTITEMSEX rather than LVM_SORTITEMSEX . Unfortunately MFC doesnt have a wrapper for this, so you have to add it yourself (maybe added in MFC7?). With the ex version, the comparison function gets given the indexes rather than the item data.
<code>
BOOL CMyListCtrl::SortItemsEx(PFNLVCOMPARE pfnCompare, DWORD dwData)
{
ASSERT(::IsWindow(m_hWnd));
ASSERT((GetStyle() & LVS_OWNERDATA)==0);
return (BOOL) ::SendMessage(m_hWnd, LVM_SORTITEMSEX, dwData, (LPARAM)pfnCompare);
}
</code>
--
AnthonyJ@planetquake.com
www.btinternet.com/~AnthonyJ
|
|
|
|
|
Cool, I did not know aboutit.
/yawar
|
|
|
|
|
Thank you very much for this info on CListCtrl::SortItemsEx() and LVM_SORTITEMSEX , it's speeded up the sorting of a large list by a factor of more than 10 (because I don't have to call CListCtrl::FindItem() all the time).
As you say, CListCtrl::SortItemsEx() doesn't seem to be in MFC6, but neither is LVM_SORTITEMSEX . A google search suggests #define LVM_SORTITEMSEX (LVM_FIRST + 81) , which seems to work.
Cheers,
Chris.
|
|
|
|
|
Hi AnthonyJ,
very very nice. fantastic tip!
Furthermore I saw you can also use the following macro and don't need to extend your list class:
#define ListView_SortItemsEx(hwndLV, _pfnCompare, _lPrm) \
(BOOL)SNDMSG((hwndLV), LVM_SORTITEMSEX, (WPARAM)(LPARAM)(_lPrm), (LPARAM)(PFNLVCOMPARE)(_pfnCompare))
makes it even more easy to use...
regards,
mykel
If they give you lined paper, write the other way!
|
|
|
|
|
How to get the current choise using ur class? Like when the user doubleclick or click on the row, there is GetCurSel() in the CListBox class to get the choice or selection made by the user. How can i do that with ur class?
Thanks
Desvario
And is a nice class, by the way...
|
|
|
|
|
Like in other CListCtrl, you just have to call to "GetNextItem":
int hIndex = GetNextItem(-1, LVNI_SELECTED);
You will get an index to the position where the user cursor is. You must call to "GetItemText" to get the text content.
----------------------------
Ivan Gadea Saez
Senior Software developer
|
|
|
|
|
great code, thanks alot for it.
When i refresh my listitems, by deleting all and adding them again, how can i stay in the previous "sort" mode?
|
|
|
|
|
ItemData* pid = new ItemData;
in _AssignNewItemData(...)
every sort, "new" so large!!!
|
|
|
|
|
Hi,
In View, I am unable to successfully create an object of CSortListCtrl.
The statement CSortListCtrl m_ListCtrl; or m_ListCtrl = new CSortListCtrl; returns a NULL for m_ListCtrl. So am not able to reference any of its functions in View. I wish someone could show me how!!
Pete
|
|
|
|
|
Try to select the list control (make the focus on the list) and Minimize and maximize the dialog.
The list control disappeared !!
A. D
|
|
|
|
|
hello,
I am trying to select a row in a SortListCtrl to no avail...
<br />
mySortListCtrl.SetItemState(indexToSelect, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);<br />
does SortListCtrl behave the same way as the ClistCtrl with dynamic selecting of its items?
thanks!
|
|
|
|
|
you can do that like this
m_List1.SetItemState(idx, LVIS_SELECTED, LVIS_SELECTED);
m_List1.SetFocus();
|
|
|
|
|
|
I'm sure I'm totally missing the forest for the trees here (it's 1am), but here's what I'm trying to do. I've added the check box style to this list control. I'm trying to iterrate through the list seeing which ones have been checked and getting the text.
Thanks in advance,
Mike Ellertson
|
|
|
|
|
hi,
that code does not change the DrawItem-function. just added some code to the OnPaint-handler ;o)
first you have to remove the owner-draw style from the header-ctrl in the SetSortArrow-function:
<br />
<br />
(just comented it out)
than ad an OnPaint-Handler in your SortHeaderCtrl.x
SortHeaderCtrl.h:
<br />
afx_msg void OnPaint();<br />
<br />
DECLARE_MESSAGE_MAP()<br />
SortHeaderCtrl.cpp:
<br />
void CSortHeaderCtrl::OnPaint() {<br />
CHeaderCtrl::OnPaint();<br />
<br />
if(m_iSortColumn < 0)<br />
return;<br />
<br />
CRect pRect;<br />
<br />
TCHAR szText[ 256 ];<br />
HD_ITEM hditem;<br />
<br />
hditem.mask = HDI_TEXT | HDI_FORMAT;<br />
hditem.pszText = szText;<br />
hditem.cchTextMax = 255;<br />
<br />
GetItem(m_iSortColumn, &hditem);<br />
<br />
CString ItemText = hditem.pszText;<br />
<br />
GetItemRect(m_iSortColumn, &pRect);<br />
<br />
CDC *pDC = GetDC();<br />
<br />
CPoint pPoints[3];<br />
<br />
CSize sz = pDC->GetTextExtent(ItemText);<br />
int x = pRect.left + sz.cx + 5;<br />
int y = (pRect.bottom / 2) - 3;<br />
<br />
pRect.right -= 2;<br />
CRgn pRgn;<br />
pRgn.CreateRectRgnIndirect(pRect);<br />
pDC->SelectClipRgn(&pRgn);<br />
<br />
if( m_bSortAscending )<br />
{<br />
pPoints[0] = CPoint(x + 5, y);<br />
pPoints[1] = CPoint(x + 10, y + 5);<br />
pPoints[2] = CPoint(x, y + 5);<br />
} else {<br />
pPoints[0] = CPoint(x, y);<br />
pPoints[1] = CPoint(x + 10, y);<br />
pPoints[2] = CPoint(x + 5, y + 5);<br />
}<br />
pDC->Polygon(pPoints, 3);<br />
}<br />
the important thing is of course to call CHeaderCtrl::OnPaint(); bevore you do your own painting stuff.
play around wit the Pen and Brush-colors for the polygon (the arrow) - i left this out, so the arrow is white and the border of it is black. looks good i think.
have fun!
vertex
|
|
|
|
|
It seems that you've forgotten to call ReleaseDC(pDC) at the end of OnPaint.
Anyway, thanks for your great code!
|
|
|
|
|
To insert a variable icon before each row, i must use the function CListCtrl::InsertItem(),just like this:
<br />
<br />
m_images.Create(IDB_CHECK_BOX,13,1,RGB(255,255,255));<br />
m_listctrl.SetImageList(&m_images,LVSIL_STATE);<br />
<br />
LVITEMA lvi;<br />
lvi.iSubItem=0;<br />
lvi.mask=LVIF_TEXT|LVIF_STATE;<br />
lvi.stateMask=LVIS_STATEIMAGEMASK;<br />
lvi.iItem=i;<br />
lvi.pszText="some words";<br />
lvi.state=INDEXTOSTATEIMAGEMASK(UNCHECKED_INDEX);<br />
m_listctrl.InsertItem(&lvi); <br />
then i use your function SetItemText(),but it generates a bug,so i add a member funtion AddItem2(LPCTSTR pszText, ... ) to match the function CListCtrl::InsertItem(),as follows:
<br />
int CSortListCtrl::AddItem2( LPCTSTR pszText, ... )<br />
{<br />
const int iIndex = GetItemCount() - 1;<br />
<br />
LPTSTR* arrpsz = new LPTSTR[ m_iNumColumns ];<br />
arrpsz[ 0 ] = new TCHAR[ lstrlen( pszText ) + 1 ];<br />
(void)lstrcpy( arrpsz[ 0 ], pszText );<br />
<br />
va_list list;<br />
va_start( list, pszText );<br />
<br />
for( int iColumn = 1; iColumn < m_iNumColumns; iColumn++ )<br />
{<br />
pszText = va_arg( list, LPCTSTR );<br />
ASSERT_VALID_STRING( pszText );<br />
VERIFY( CListCtrl::SetItem( iIndex, iColumn, LVIF_TEXT, pszText, 0, 0, 0, 0 ) );<br />
<br />
arrpsz[ iColumn ] = new TCHAR[ lstrlen( pszText ) + 1 ];<br />
(void)lstrcpy( arrpsz[ iColumn ], pszText );<br />
}<br />
<br />
va_end( list );<br />
<br />
VERIFY( SetTextArray( iIndex, arrpsz ) );<br />
<br />
return iIndex;<br />
}<br />
In addition, your code can not sort float, so i add two member function , as follows:
<br />
bool IsFloat( LPCTSTR pszText )<br />
{<br />
ASSERT_VALID_STRING( pszText );<br />
<br />
for( int i = 0; i < lstrlen( pszText ); i++ )<br />
if( !_istdigit( pszText[ i ] ) && pszText[i] != '.')<br />
return false;<br />
<br />
return true;<br />
}<br />
<br />
<br />
int FloatCompare( LPCTSTR pszNumber1, LPCTSTR pszNumber2 )<br />
{<br />
ASSERT_VALID_STRING( pszNumber1 );<br />
ASSERT_VALID_STRING( pszNumber2 );<br />
<br />
const float iNumber1 = atof( pszNumber1 );<br />
const float iNumber2 = atof( pszNumber2 );<br />
<br />
if( iNumber1 < iNumber2 )<br />
return -1;<br />
<br />
if( iNumber1 > iNumber2 )<br />
return 1;<br />
<br />
return 0;<br />
}<br />
<br />
i just make a little modification based on your codes.
your codes is very good, thank you for sharing these codes.
sea_soul
|
|
|
|
|
while creating the control dynamically like this :
m_pList = new CSortListCtrl;
m_pList->Create(LVS_REPORT|WS_CHILD|WS_VISIBLE,Rect,this, 0x1000 );
it crashes while running the debug version. The Create() calls CSortListCtrl::PreSubclassWindow() and the problematic line is:
VERIFY( m_ctlHeader.SubclassWindow( GetHeaderCtrl()->GetSafeHwnd() ) );
What can be done ? Why is that happenning?
|
|
|
|
|
I've got the same result in creating the control dynamically.
The GetHeaderCtrl() call in PreSubclassWindow fails because it returns NULL pointer.
So, I subclassed the CHeaderCtrl in the parent window.
I think that would work.
....
m_pListCtrl = new CSortListCtrl;
m_pListCtrl->Create(LVS_REPORT|WS_CHILD|WS_VISIBLE|LVS_SHOWSELALWAYS|LVS_EDITLABELS,
CRect(0, 0, 0, 0), this, 0);
VERIFY( m_pListCtrl->m_ctlHeader.SubclassWindow( m_pListCtrl->GetHeaderCtrl()->GetSafeHwnd() ) );
....
The code above part of the create function in the parent window.
|
|
|
|
|