关于ListView的NM_CUSTOMDRAW消息处理

发布时间:2014年3月6日 作者:未知 查看次数:2048

关于ListView的NM_CUSTOMDRAW消息处理


用ListView的NM_CUSTOMDRAW消息处理可以实现设置每行显示不同的颜色,我在用masm32中遇到了点波折。

(一)----------------

资料:

http://hi.baidu.com/wujunhua861128/item/c7fa4742bd11dc14886d1043

【转】NM_CUSTOMDRAW消息处理函数

原理:
(1)在NM_CUSTOMDRAW消息处理函数中根据 dwDrawStage 状态来编写不同的处理代码
(2)主要是判断 CDDS_ITEMPREPAINT 状态
(3)不要在对话框资源管理器中设置CListCtrl控件的Owner draw fixed属性,否则在Debug模式下报错。

方法一,使用NM_CUSTOMDRAW消息映射
(1)声明消息处理函数:afx_msg void OnCustomDrawList ( NMHDR* pNMHDR, LRESULT* pResult );
(1)手动添加消息映射:ON_NOTIFY_REFLECT ( NM_CUSTOMDRAW, OnCustomDrawList )
(2)编写自绘消息处理函数

void CListCtrl2::OnCustomDrawList ( NMHDR* pNMHDR, LRESULT* pResult )
{     
     NMLVCUSTOMDRAW*    pLVCD   =    reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);   
    *pResult = CDRF_DODEFAULT;   
     int rowIndex = static_cast<int>(pLVCD->nmcd.dwItemSpec);
    if(pLVCD->nmcd.dwDrawStage == CDDS_PREPAINT)   
    {   
        *pResult = CDRF_NOTIFYITEMDRAW;   
     }   
    else if (pLVCD->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)   
    {     
        *pResult = CDRF_NOTIFYSUBITEMDRAW;   
     }//画项
    else if (pLVCD->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_SUBITEM))   
    {
        int nitem = static_cast<int>    (pLVCD->nmcd.dwItemSpec);   
        int nsubitem = pLVCD->iSubItem;   
         CDC* pDC = CDC::FromHandle(pLVCD->nmcd.hdc);  
         CString str;   
         CRect rect;
         CSize msize;
         UINT nFormat = DT_VCENTER | DT_SINGLELINE;   
         rect.left += 3; //调整自绘输出位置   
        //get    rect    of    the    sub    item    which    is    going    to    paint   
         GetSubItemRect(nitem,nsubitem,LVIR_BOUNDS,rect);     
        //get    the    context    is    going    to    paint    on    the    subitem   
         str = GetItemText(nitem,nsubitem);   

        //如果选中
        if(LVIS_SELECTED == this->GetItemState(rowIndex,LVIS_SELECTED)) //判断当前项是否选中   
        {
            //所画项是选中项   
             pDC->SetTextColor(RGB(217,60,40));//字体颜色,因该是白色   
             pDC->FillSolidRect(&rect,RGB(86,125,228));//背景,深蓝色吧,试试               
         }   
        else   
        {
             pDC->SetTextColor(RGB(0,0,0));//字体颜色,因该是白色   
             pDC->FillSolidRect(&rect,RGB(255,255,255));//背景,深蓝色吧,试试   
         }
         msize = pDC->GetTextExtent(str);
         pDC->DrawText(str,   &rect,    nFormat); //自绘输出
        *pResult = CDRF_SKIPDEFAULT;
     }
}

(二)----------------

资料:

http://www.360doc.com/content/11/0710/22/6165138_132792986.shtml

NM_CUSTOMDRAW消息解释

common control 4.7版本介绍了一个新的特性叫做Custom Draw,这个名字显得模糊不清,让人有点摸不着头脑,而且MSDN里也只给出了一些如风的解释和例子,没有谁告诉你你想知道的,和究竟这个特性有什么好处。

Custom draw可以被想象成一个轻量级的,容易使用的重绘方法(重绘方法还有几种,例如Owner Draw等)。这种容易来自于我们只需要处理一个消息(NM_CUSTOMDRAW),就可以让Windows为你干活了,你就不用被逼去处理"重绘过程"中所有的脏活了。

这篇文章的焦点是如何在一个LISTCTRL控件上使用Custom Draw消息。究其原因,一部分是因为我已经在我的工作上使用了Custom Draw有一段时间了,我很熟悉它。另一个原因是这个机制确实是非常好用,你只需要写很少量的代码就可以达到很好的效果。使用 Custom draw 来对控件外观编程甚至可以代替很多的古老方法。

以下代码是在WIN98 VC6 SP2的环境下写的,common controls DLL的版本是5.0。我已经对其在WinNT 4上进行了测试。系统要运行这些代码,它的common controls DLL的版本必须至少是4.71。但随着IE4 的发布,这已经不是问题了。(IE会夹带着这个DLL一起发布)

 

Custom Draw 基础

我将会尽我所能把Custom Draw的处理描述清楚,而不是简单的引用MSDN的文档。这些例子都需要你的程序有一个ListCtrl在对话框上,并且这个ListCtrl处于Report和多列模式。

 

Custom Draw 的消息映射入口

Custom draw 是一个类似于回调的处理过程,Windows在绘制List Ctrl的某个时间点上通过 Notification 消息通知你的程序,你可以选择忽略所有的通知(这样你就会看到标准的ListCtrl),或者处理某部分的绘制(实现简单的效果),甚至整个的控件都由你来绘制(就象使用Owner-Drawing一样)。这个机制的真正卖点是:你只需要实现一些你需要的,其余的可以让Windows为你代劳。

好了,现在你可以开始为你的ListCtrl添加Custom Draw去做一些个性化的事情了。你首先要有正确的Comm Ctrl Dll版本,然后Windows会为你发送NM_CUSTOMDRAW消息,你只需要添加一个处理函数以便开始使用Custom draw。首先添加一个消息映射,象下面一样:

ON_NOTIFY ( NM_CUSTOMDRAW, IDC_MY_LIST, OnCustomdrawMyList )

处理函数的原形如下:

afx_msg void OnCustomdrawMyList ( NMHDR* pNMHDR, LRESULT* pResult );

这就告诉MFC你要处理从你的ListCtrl控件发出的WM_NOTIFY消息,IDIDC_MY_LIST,通知码为NM_CUSTOMDRAWOnCustomdrawMyList就是你的处理函数。

 

如果你有一个从ClistCtr派生的类,你想为它添加custom draw,你就可以使用ON_NOTIFY_REFLECT来代替。如下:

ON_NOTIFY_REFLECT ( NM_CUSTOMDRAW, OnCustomdraw )OnCustomdraw的原形和上面的函数一致,但它是声明在你的派生类里的。

Custom draw将控件的绘制分为两部分:擦除和绘画。Windows在每部分的开始和结束都会发送NM_CUSTOMDRAW消息。所以总共就有4个消息。但是实际上你的程序所收到消息可能就只有1个或者多于四个,这取决于你想要让WINDOWS怎么做。每次发送消息的时段被称作为一个绘画段。你必须紧紧抓住这个概念,因为它贯穿于整个重绘的过程。

 

所以,你将会在以下的时间点收到通知:

 

l         一个item被画之前——“绘画前l         一个item被画之后——“绘画后l         一个item被擦除之前——“擦除前l         一个item被擦除之后——“擦除后

 

并不是所有的消息都是一样有用的,实际上,我不需要处理所有的消息,直到这篇文章完成之前,我还没使用过擦除前和擦除后的消息。所以,不要被这些消息吓到你。

NM_CUSTOMDRAW Messages提供给你的信息:

l         NM_CUSTOMDRAW消息将会给你提供以下的信息:l         ListCtrl的句柄l         ListCtrlIDl         当前的绘画段”l         绘画的DC,让你可以用它来画画l         正在被绘制的控件、itemsubitemRECTl         正在被绘制的ItemIndexl         正在被绘制的SubItemIndexl         正被绘制的Item的状态值(selected, grayed,等等l         ItemLPARAM值,就是你使用CListCtrl::SetItemData所设的那个值

 

上述所有的信息对你来说可能都很重要,这取决于你想实现什么效果,但最经常用到的就是绘画段绘画DC“Item Index”“LPARAM”这几个值。

一个简单的例子:

好了,经过上面的无聊的细节之后,我们是时候来看一些简单的代码了。第一个例子非常的简单,它只是改变了一下控件中文字的颜色。

 

处理的代码如下:

void CPanel1::OnCustomdrawList ( NMHDR* pNMHDR, LRESULT* pResult )

{

 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );

// Take the default processing unless we set this to something else below.
    *pResult = 0;
    // First thing - check the draw stage. If it's the control's prepaint
    // stage, then tell Windows we want messages for every item.
    if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
        {
        *pResult = CDRF_NOTIFYITEMDRAW;
        }
    else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
        {
        // This is the prepaint stage for an item. Here's where we set the
        // item's text color. Our return value will tell Windows to draw the
        // item itself, but it will use the new color we set here.
        // We'll cycle the colors through red, green, and light blue.
        COLORREF crText;        if ( (pLVCD->nmcd.dwItemSpec % 3) == 0 )
            crText = RGB(255,0,0);
        else if ( (pLVCD->nmcd.dwItemSpec % 3) == 1 )
            crText = RGB(0,255,0);
        else
            crText = RGB(128,128,255);        // Store the color back in the NMLVCUSTOMDRAW struct.
        pLVCD->clrText = crText;        // Tell Windows to paint the control itself.
        *pResult = CDRF_DODEFAULT;
        }
}
结果如下,你可以看到行和行间的颜色的交错显示,多酷,而这只需要两个if的判断就可以做到了。

有一件事情必须记住,在做任何的绘画之前,你都要检查正处身的绘画段,因为你的处理函数会接收到非常多的消息,而绘画段将决定你代码的行为。

一个更小的简单例子:    下面的例子将演示怎么去处理subitem的绘画(其实subitem也就是列)ListCtrl控件绘画前处理NM_CUSTOMDRAW消息。告诉Windows我们想对每个Item处理NM_CUSTOMDRAW消息。当这些消息中的一个到来,告诉Windows我们想在每个SubItem的绘制前处理这个消息当这些消息到达,我们就为每个SubItem设置文字和背景的颜色。

这里需要注意两件事:

l        clrTextBk的颜色只是针对每一列,在最后一列的右边那个区域颜色也还是和ListCtrl控件的背景颜色一致。l         当我重新看文档的时候,我注意到有一篇题目是NM_CUSTOMDRAW(list view)的文章,它说你可以在最开始的custom draw消息中返回CDRF_NOTIFYSUBITEMDRAW就可以处理SubItem了,而不需要在CDDS_ITEMPREPAINT绘画段中去指定CDRF_NOTIFYSUBITEMDRAW。但是我试了一下,发现这种方法并不起作用,你还是需要处理CDDS_ITEMPREPAINT段。

 

(三)----------------

网上大多都是用VC类,进行消息映射,查了许多关于NM_CUSTOMDRAW消息方法都是这样的,可我试了N多次也没有成功,用SPY也没找到。我想目前不应该是版本的问题。

再用子类化ListView控件下的WM_NOTIFY,却发现标题头颜色改了,单Item行没变。(无意的新发现)

后来在http://www.orcode.com/article/List_20113757.html(好像自动翻译的读起来不怎么通)下看到了这么一段:

ListView的颜色
你不会相信花了多少时间,我挖起来,以实际工作!我的意思是,MFC中只有99.99%的例子!
所以在这里我们去,最后,纯WINAPI ListView的颜色代码。
注意:此代码是MSDN发现里面深刻!
首先,我们处理WM_NOTIFY消息,使用NM_CUSTOMDRAW通知。我们不需要使用任何绘制ListView的所有者,因为这是自定义的绘制,我们可以绘制我们的意志的项目/子项目。

if(((LPNMHDR)lParam)->code == NM_CUSTOMDRAW)
{
   SetWindowLong(hWnd, DWL_MSGRESULT, 
        (LONG)ProcessCustomDraw(lParam));
   return TRUE;
}
......

DWL_MSGRESULT?

DWL_MSGRESULT:设置对话框过程中处理的消息的返回值。
 

改代码,终于成功。原来要设欢声对话框的消息返回值。

 

(四)----------------

资料:英文

http://blog.csdn.net/uuzhang/article/details/708697

api 编写 listview(ListControl)

Using ListView control under Win32 API 
  
  
 Submitted by Isaiah.Net on Sunday, October 23, 2005 - 23:58  
 
--------------------------------------------------------------------------------

Introduction

This article is for those who did not find, any useful code examples and information regarding ListControl (ListView). For me, it was really hard to get a working code for Win32 API, since I don't code in MFC. The only help I was able to get was win32.hlp which contains every API by Microsoft. Note that it did not have any examples in it. Also searching through forums can be helpful, but it is slow and sometimes you really wanna get some stuff going fast and clear. I decided to write this article for the coders who don't know any MFC (class) or just starting with ListControl via APIs. First, I must say that all examples I've seen so far lacked information for mon-MFC coders!. If you can't code or can't understand MFC, and are using the API, then this little article is for you.

So let's start.

#include

Add it into your C/CPP file so we can use its functions. We add a ListControl to our dialog (you can also create it via the CreateWindowEx API!). Once we named it, (IDC_LIST), we add 2 buttons which will do something to our ListView. Once we've done it, we need to save its HANDLE (which is HWND). We use a static HWND hList=NULL; as a global variable (so we can access it in any function). Now we need to initialize our ListView (I will do it in the WM_INITDIALOG message). First we get the handle: hList=GetDlgItem(hWnd,IDC_LIST);, then we add a LVITEM LvItem; struct (also a global variable). The LVITEM struct consists of a few parameters:

typedef struct _LV_ITEM {
UINT mask; // attributes of this data structure
int iItem; // index of the item to which this structure refers
int iSubItem; // index of the subitem to which this structure refers
UINT state; // Specifies the current state of the item
UINT stateMask; // Specifies the bits of the state member that are valid.
LPTSTR pszText; // Pointer to a null-terminated string
// that contains the item text
int cchTextMax; // Size of the buffer pointed to by the pszText member
int iImage; // index of the list view item's icon
LPARAM lParam; // 32-bit value to associate with item
} LV_ITEM;

We also add a LVCOLUMN LvCol; (global variable). The LVCOLUMN struct consists of a few parameters:

typedef struct _LV_COLUMN {
UINT mask; // which members of this structure contain valid information
int fmt; // alignment of the column heading and the subitem text
int cx; // Specifies the width, in pixels, of the column.
LPTSTR pszText; // Pointer to a null-terminated string
// that contains the column heading
int cchTextMax; // Specifies the size, in characters, of the buffer
int iSubItem; // index of subitem
} LV_COLUMN;

Now that we know about the struct's members, let's initialize and use what we need: at the WM_INITDIALOG message.

// Here we put the info on the Coulom headers
// this is not data, only name of each header we like
Memset(&LvCol,0,sizeof(LvCol)); // Zero Members
LvCol.mask=LVCF_TEXT|LVCF_WIDTH|LVCF_SUBITEM; // Type of mask
LvCol.cx=0x28; // width between each coloum
LvCol.pszText="Item"; // First Header Text
LvCol.cx=0x42; // width of column

For more information that our list control can supply us, we can use the extended styles (full raw select):

SendMessage(hList,LVM_SETEXTENDEDLISTVIEWSTYLE,
0,LVS_EX_FULLROWSELECT); // Set style

Now, let us put some columns (as much as you like):

// Inserting Couloms as much as we want
SendMessage(hList,LVM_INSERTCOLUMN,0,(LPARAM)&LvCol); // Insert/Show the coloum
LvCol.pszText="Sub Item1"; // Next coloum
SendMessage(hList,LVM_INSERTCOLUMN,1,(LPARAM)&LvCol); // ...
LvCol.pszText="Sub Item2"; //
SendMessage(hList,LVM_INSERTCOLUMN,2,(LPARAM)&LvCol); //
LvCol.pszText="Sub Item3"; //
SendMessage(hList,LVM_INSERTCOLUMN,3,(LPARAM)&LvCol); //
LvCol.pszText="Sub Item4"; //
SendMessage(hList,LVM_INSERTCOLUMN,4,(LPARAM)&LvCol); //
LvCol.pszText="Sub Item5"; //
SendMessage(hList,LVM_INSERTCOLUMN,5,(LPARAM)&LvCol); // ...same as above

Once we've added headers (columns) to our listview, we need to add some items to it. This is done via the above mentioned LVITEM struct.

memset(&LvItem,0,sizeof(LvItem)); // Zero struct's Members

// Setting properties Of members:

LvItem.mask=LVIF_TEXT; // Text Style
LvItem.cchTextMax = 256; // Max size of test
LvItem.iItem=0; // choose item
LvItem.iSubItem=0; // Put in first coluom
LvItem.pszText="Item 0"; // Text to display (can be from a char variable) (Items)

SendMessage(hList,LVM_INSERTITEM,0,(LPARAM)&LvItem); // Send info to the Listview

for(i=1;i<=5;i++) // Add SubItems in a loop
{
LvItem.iSubItem=i;
sprintf(Temp,"SubItem %d",i);
LvItem.pszText=Temp;
SendMessage(hList,LVM_SETITEM,0,(LPARAM)&LvItem); // Enter text to SubItems
}

How about inserting a new item?

// lets add a new Item:

LvItem.iItem=1; // choose item
LvItem.iSubItem=0; // Put in first coluom
LvItem.pszText="Item 1"; // Text to display

SendMessage(hList,LVM_INSERTITEM,0,(LPARAM)&LvItem); // Send to the Listview

for(i=1;i<=5;i++) // Add SubItems in a loop
{
LvItem.iSubItem=i;
sprintf(Temp,"SubItem %d",i);
LvItem.pszText=Temp;
SendMessage(hList,LVM_SETITEM,0,(LPARAM)&LvItem); // Enter text to SubItems
}

Note: Temp is a char variable: char Temp[255]="".

Adding an item via the button:

case IDC_ADDITEM:
{
int iItem;
char ItemText[100];

iItem=SendMessage(hList,LVM_GETITEMCOUNT,0,0); // number of items

GetDlgItemText(hWnd,IDC_ADD,ItemText,100); // get text from editobx

if((lstrlen(ItemText))==0) // check if items exist
{
MessageBox(hWnd,"Please Write Some Text",
"Error",MB_OK|MB_ICONINFORMATION);
break;
}

LvItem.iItem=iItem; // choose item to enter to
LvItem.iSubItem=0; // Put in first coluom (no need subitems)

// Text to display (can be from a char variable) (Items)
LvItem.pszText=ItemText;
// Send item text to the Listview
SendMessage(hList,LVM_INSERTITEM,0,(LPARAM)&LvItem);
}
break;

Adding a Subitem via the button:

case IDC_ADDSUBITEM:
{
int Item,i;
char SubItemText[100];

Item=SendMessage(hList,LVM_GETITEMCOUNT,0,0); // number of items

GetDlgItemText(hWnd,IDC_ADDSUB,SubItemText,100); // get text from editobx

if((lstrlen(SubItemText))==0) // check if items exist
{
MessageBox(hWnd,"Please Write Some Text",
"Error",MB_OK|MB_ICONINFORMATION);
break;
}

// choose the item-1 (we can't add subitems to non existing item
LvItem.iItem=Item-1;

for(i=1;i<=5;i++) // add 5 subitems
{
// Text to display (can be from a char variable) (Items)
LvItem.pszText=SubItemText;
// Put in coluom of index i
LvItem.iSubItem=i;

SendMessage(hList,LVM_SETITEM,0,
(LPARAM)&LvItem); // send Subitem text to listview
}
}
break;

Put both item and SubItems.

case IDC_BOTH:
{
int itemIndex,j;
char iSubItemText[100]="";
char iItemText[100]="";

itemIndex=SendMessage(hList,LVM_GETITEMCOUNT,0,0); // number of items

GetDlgItemText(hWnd,IDC_ADD,iItemText,100); // get text
GetDlgItemText(hWnd,IDC_ADDSUB,iSubItemText,100); // get text

// we enter text to the editboxes?
if((lstrlen(iSubItemText) && lstrlen(iItemText))==0)
{
MessageBox(hWnd,"Please Write Some Text",
"Error",MB_OK|MB_ICONINFORMATION);
break;
}

LvItem.iItem=itemIndex; // item will be put at itemIndex
LvItem.iSubItem=0; // adding item, no need subitems
LvItem.pszText=iItemText; // set pointer to the item text
SendMessage(hList,LVM_INSERTITEM,0,(LPARAM)&LvItem); // put it

for(j=1;j<=5;j++) // add 5 subitems
{
LvItem.pszText=iSubItemText; // Text to display
LvItem.iSubItem=j; // Put in coluom at index j

SendMessage(hList,LVM_SETITEM,0,(LPARAM)&LvItem); // put it :)
}
}
break;

Deleting all items at the list control is done via one message to the list control:

SendMessage(hList,LVM_DELETEALLITEMS,0,0);

Deleting Specific Selected Item in the ListView.

We use the flag (global variable) to help us notice when an item is focused or selected (has a blue color wrapping the item).

case IDC_DELSELITEM:
if(flag) // we pressed an item?
SendMessage(hList,LVM_DELETEITEM,iSelect,0); // delete the item selected

flag=0; // reset the flag after we used the item
break;

The next part is to show how a user selects an item and responds to the button or how do we use the mouse and affect the list view while clicking on it with double click, left click.. etc.

Affecting the ListView must be done via the WM_NOTIFY message, this is because the ListView doesn't have it over its properties like ListBox has! We put the WM_NOTIFY message under the switch(Message){...}.

case WM_NOTIFY: // the message that is being sent always
{
switch(LOWORD(wParam)) // hit control
{
case IDC_LIST: // did we hit our ListView contorl?

To make us select the items (we are using full raw select), we need to use NMHDR and its members:

// if code == NM_CLICK - Single click on an item
if(((LPNMHDR)lParam)->code == NM_CLICK)
{
// do some stuff here
}

A list of code we can use for our listview are:

...
...
NM_CLICK - pnmh = (NMHDR FAR *) lParam;
NM_DBLCLK - pnmh = (NMHDR FAR *) lParam;
NM_KILLFOCUS - pnmh = (NMHDR FAR *) lParam;
NM_RCLICK - pnmh = (NMHDR FAR *) lParam;
NM_RDBLCLK - pnmh = (NMHDR FAR *) lParam;
NM_RETURN - pnmh = (NMHDR FAR *) lParam;
NM_SETFOCUS - pnmh = (NMHDR FAR *) lParam;
LVN_ENDLABELEDIT - pdi = (LV_DISPINFO FAR *) lParam;
LVN_BEGINLABELEDIT -pdi = (LV_DISPINFO FAR *) lParam;
...
...

See win32.hlp for more useful messages.

So, let's continue from what we left, we need to select an item in order to delete it. We are using the code we learned/used from above:

if(((LPNMHDR)lParam)->code == NM_CLICK)
{
iSelect=SendMessage(hList,LVM_GETNEXTITEM,
-1,LVNI_FOCUSED); // return item selected

if(iSelect==-1) // no items
{
MessageBox(hWnd,"No Items in ListView",
"Error",MB_OK|MB_ICONINFORMATION);
break;
}

flag=1; // set flag so that we know there was a hit
}

Note: iSelect is a global variable.

Say we want to get the text from an item & subitems once we double click an item. We need to add a NM_DBLCLK message and process it with our code:

if(((LPNMHDR)lParam)->code == NM_DBLCLK)
{
//...
}

We use all the messages that the listview has to offer us and from the code we used above, it wont be hard to get the item/sub items:

case WM_NOTIFY:
{
switch(LOWORD(wParam))
{
case IDC_LIST:
if(((LPNMHDR)lParam)->code == NM_DBLCLK)
{
char Text[255]={0};
char Temp[255]={0};
char Temp1[255]={0};
int iSlected=0;
int j=0;

iSlected=SendMessage(hList,LVM_GETNEXTITEM,-1,LVNI_FOCUSED);

if(iSlected==-1)
{
MessageBox(hWnd,"No Items in ListView",
"Error",MB_OK|MB_ICONINFORMATION);
break;
}

memset(&LvItem,0,sizeof(LvItem));
LvItem.mask=LVIF_TEXT;
LvItem.iSubItem=0;
LvItem.pszText=Text;
LvItem.cchTextMax=256;
LvItem.iItem=iSlected;

SendMessage(hList,LVM_GETITEMTEXT,
iSlected, (LPARAM)&LvItem);

sprintf(Temp1,Text);

for(j=1;j<=5;j++)
{
LvItem.iSubItem=j;
SendMessage(hList,LVM_GETITEMTEXT,
iSlected, (LPARAM)&LvItem);
sprintf(Temp," %s",Text);
lstrcat(Temp1,Temp);
}

MessageBox(hWnd,Temp1,"test",MB_OK);

}
}
}

Label Editing

Say we want to edit our item's text at runtime, how can we do it? We will use the LVN_BEGINLABELEDIT and LVN_ENDLABELEDIT messages.

if(((LPNMHDR)lParam)->code == LVN_BEGINLABELEDIT)
{
hEdit=ListView_GetEditControl(hList);
}

When we give the listbox an "edit labels" dialog property, we are able to edit the item's text when we press on it with the mouse [select item -> click small left button]. Once we did it, we need to trap the editbox created at run-time by the listbox. We use the LVN_BEGINLABELEDIT message to begin the label edit. Once it's trapped, we are saving the created edit's handle.

I used the ListView_GetEditControl macro because it is shorter to write :), but you can use the SendMessage as well. (Return a HWND if using HWND hEdit=(HWND)SendMessage().) We save the handle into hEdit.

Note: hEdit is a global variable!

Once we saved the handle, we need to use LVN_ENDLABELEDIT message to actually get the text from the created edit and save it over a text buffer.

if(((LPNMHDR)lParam)->code == LVN_ENDLABELEDIT)
{
int iIndex;
char text[255]="";

iIndex=SendMessage(hList,LVM_GETNEXTITEM,
-1,LVNI_FOCUSED); // get selected item

LvItem.iSubItem=0; // we get the item only (change for sub)
LvItem.pszText=text; // text type
GetWindowText(hEdit, text, sizeof(text)); // get the text into a buffer

SendMessage(hList,LVM_SETITEMTEXT,
(WPARAM)iIndex,(LPARAM)&LvItem); // put new text
}

ListView Colors

You won't believe how much time I took to dig this up to actually work! I mean, 99.99% examples were MFC only!

So here we go, finally, pure WinAPI code for listview colors.

Note: This code is found deeply inside MSDN!

First we handle the WM_NOTIFY message to use a NM_CUSTOMDRAW notification. We don't need to use any owner drawn listview, and since this is custom drawn, we can paint the items/subitems at our will.

if(((LPNMHDR)lParam)->code == NM_CUSTOMDRAW)
{
SetWindowLong(hWnd, DWL_MSGRESULT,
(LONG)ProcessCustomDraw(lParam));
return TRUE;
}

We will set a new style which will be a result of our painting function.

LRESULT ProcessCustomDraw (LPARAM lParam)
{
LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;

switch(lplvcd->nmcd.dwDrawStage)
{
case CDDS_PREPAINT : //Before the paint cycle begins
//request notifications for individual listview items
return CDRF_NOTIFYITEMDRAW;

case CDDS_ITEMPREPAINT: //Before an item is drawn
if (((int)lplvcd->nmcd.dwItemSpec%2)==0)
{
//customize item appearance
lplvcd->clrText = RGB(255,0,0);
lplvcd->clrTextBk = RGB(200,200,200);
return CDRF_NEWFONT;
}
else{
lplvcd->clrText = RGB(0,0,255);
lplvcd->clrTextBk = RGB(255,255,255);

return CDRF_NEWFONT;
}
break;

//Before a subitem is drawn
case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
if (iSelect == (int)lplvcd->nmcd.dwItemSpec)
{
if (0 == lplvcd->iSubItem)
{
//customize subitem appearance for column 0
lplvcd->clrText = RGB(255,0,0);
lplvcd->clrTextBk = RGB(255,255,255);

//To set a custom font:
//SelectObject(lplvcd->nmcd.hdc,
// );

return CDRF_NEWFONT;
}
else if (1 == lplvcd->iSubItem)
{
//customize subitem appearance for columns 1..n
//Note: setting for column i
//carries over to columnn i+1 unless
// it is explicitly reset
lplvcd->clrTextBk = RGB(255,0,0);
lplvcd->clrTextBk = RGB(255,255,255);

return CDRF_NEWFONT;
}
}
}
return CDRF_DODEFAULT;
}

There you code, change/implement code at your will. This is a very good working skeleton of a custom draw, painting items! :-)
---Sub item Colors---

Sub item colors are really cool when you don't wanna have same color for a row. The basic idea is to send a CDRF_NOTIFYSUBITEMDRAW notification to our result. We will send this message first thing when our item is going to be repainted!

switch(lplvcd->nmcd.dwDrawStage)
{
case CDDS_PREPAINT : //Before the paint cycle begins
{
//request notifications for individual listview items
return CDRF_NOTIFYITEMDRAW;
}
break;

case CDDS_ITEMPREPAINT: //Before an item is drawn
{
// Notify we want SubItems to be repainted
return CDRF_NOTIFYSUBITEMDRAW;
}
break;
}

The only thing left is to trap this notification and to start drawing the sub items with any color/back-color we want, in a single item.

switch(lplvcd->nmcd.dwDrawStage)
{
case CDDS_PREPAINT : //Before the paint cycle begins
//request notifications for individual listview items
return CDRF_NOTIFYITEMDRAW;

case CDDS_ITEMPREPAINT: //Before an item is drawn
{
return CDRF_NOTIFYSUBITEMDRAW;
}
break;

//Before a subitem is drawn
case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
{
switch(lplvcd->iSubItem)
{
case 0:
{
lplvcd->clrText = RGB(255,255,255);
lplvcd->clrTextBk = RGB(240,55,23);
return CDRF_NEWFONT;
}
break;

case 1:
{
lplvcd->clrText = RGB(255,255,0);
lplvcd->clrTextBk = RGB(0,0,0);
return CDRF_NEWFONT;
}
break;

case 2:
{
lplvcd->clrText = RGB(20,26,158);
lplvcd->clrTextBk = RGB(200,200,10);
return CDRF_NEWFONT;
}
break;

case 3:
{
lplvcd->clrText = RGB(12,15,46);
lplvcd->clrTextBk = RGB(200,200,200);
return CDRF_NEWFONT;
}
break;

case 4:
{
lplvcd->clrText = RGB(120,0,128);
lplvcd->clrTextBk = RGB(20,200,200);
return CDRF_NEWFONT;
}
break;

case 5:
{
lplvcd->clrText = RGB(255,255,255);
lplvcd->clrTextBk = RGB(0,0,150);
return CDRF_NEWFONT;
}
break;

}

}
}
return CDRF_DODEFAULT;

ListView BackGround Image

Isn't it cool to have a picture (JPG, BMP, GIF...) on your ListView color? It makes it a bit more pro look. I guess it is more about personal taste. Anyhow, the ListView will use the OLE object to connect to the image and will draw it over the control.

First, the ListView will use a struct to store information regarding the image and its properties. The LVBKIMAGE struct:

typedef struct tagLVBKIMAGE {
ULONG ulFlags; // the way the pic behaves (tile, none, normal..)
HBITMAP hbm; // reserved for future!! (HBITMAP)
LPTSTR pszImage; // path/url to our bitmap if used with LVBKIF_SOURCE_URL
UINT cchImageMax;//Size of the buffer at the address in pszImage
int xOffsetPercent; // x pos on listview
int yOffsetPercent; // y pos on listview
} LVBKIMAGE, *LPLVBKIMAGE;

First thing to do is to initialize the OLE object! Microsoft suggests 2 ways for that: CoInitialize(NULL); or OleInitialize(NULL). The code itself is pretty much self explained!

case WM_INITDIALOG:
{
LVBKIMAGE plvbki={0}; // set all members to 0 (or use memset)
char url[]="C://a.jpg"; // path to our image
//memset(&plvbki,0,sizeof(plvbki));
plvbki.ulFlags=LVBKIF_SOURCE_URL; // image from a path
plvbki.pszImage=url; // the path
plvbki.xOffsetPercent=40; // position
plvbki.yOffsetPercent=15; // on the list view
OleInitialize(NULL); // intialize OLE COM Object
SendMessage(hList,LVM_SETTEXTBKCOLOR,
0,(LPARAM)CLR_NONE); // see below
SendMessage(hList,LVM_SETBKIMAGE,0,
(LPARAM)(LPLVBKIMAGE)&plvbki); // do it
}
break;

You can see that I have used CLR_NONE. Now, if your list view is not using custom control (Listview colors, see section above), then this line will set the background color of each row to transparent!

The only thing left to do is to close the OLE COM object. This is what Microsoft suggested anyway, doing it by: CoUninitialize(); or OleUninitialize();. When your application is closed, funny, it causes a crash here. I'll check it later :-)
---Adding Item's Renaming Cancel Event---

Heya, ever wanted that effect when you edit a folder's name and you typed the wrong name, and wanna start over and go back to the original folder's name by ESC key? (you probably know it..)

We want to add the same effect to our listview's items too! For this, we need to learn about Windows messages and process them. Usually think about an EverLasting look that monitors the window you are using, and listens for every message that you process:

Mouse, KeyBoard...etc., you should already be familiar with them. This is what we need to to:

1. Save the original text of item
2. Monitor for a key press
3. If ESC key pressed, re-save old text else

we get the new text and show it on the new item's caption.

Let's start by creating our *Global* vars (so we can access then from any function/message).

//*Global Variables*
bool escKey=0; // check whatever we pressed ESC
char tempstr[100]=""; // holds original text
TCHAR tchar; // holds the char we pressed (WM_CHAR message)
MSG msg; // Message struct to be filled

And now for the re-coding & adding some stuff:

We first begin with adding the below code to the WM_INITDIALOG:

ShowWindow(hWnd,SW_NORMAL);
UpdateWindow(hWnd);

while(TRUE)
{
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
// We must Process this! this is our ticket out of the loop & process
if(msg.message==WM_QUIT)
{
break; // Kill while look
}
TranslateMessage(&msg); // Translate the Message (VK->WM_CHAR..etc)
DispatchMessage(&msg); // dispatches a message to a window procedure
}
}

Notice the WM_QUIT. To break the while loop, we must process this when a user hits the [X]. The while will be broken and we will go to the WM_CLOSE message where we must add 1 line:

case WM_CLOSE:
{
PostQuitMessage(0);
EndDialog(hWnd,0); // kill dialog
}
break;

Now for the main part, you already know the messages that Windows sends to the ListView in order to edit an item's caption right?

* LVN_BEGINLABELEDIT
* LVN_ENDLABELEDIT

We already have a code for them, but we need to add a slight addition:

if(((LPNMHDR)lParam)->code == LVN_BEGINLABELEDIT)
{
hEdit=ListView_GetEditControl(hList);
GetWindowText(hEdit, tempstr, sizeof(tempstr)); // NEW
}

Here we save the text before we actually edit it! That's great and the last part:

if(((LPNMHDR)lParam)->code == LVN_ENDLABELEDIT)
{
int iIndex;
char text[255]="";

tchar = (TCHAR)msg.wParam;// NEW
if(tchar == 0x1b) // NEW
escKey=1; // NEW

iIndex=SendMessage(hList,LVM_GETNEXTITEM,-1,LVNI_FOCUSED);
if(iIndex==-1)
break;
LvItem.iSubItem=0; // main item only

if(escKey==0) // NEW
{
LvItem.pszText=text;
GetWindowText(hEdit, text, sizeof(text));
SendMessage(hList,LVM_SETITEMTEXT,
(WPARAM)iIndex,(LPARAM)&LvItem);
}
else{ // All Below is NEW
LvItem.pszText=tempstr; // put old text on item's caption
SendMessage(hList,LVM_SETITEMTEXT,
(WPARAM)iIndex,(LPARAM)&LvItem);
escKey=0;
}
}
break;

We first begin by checking tchar's content...why?

Ok, well, you probably know that ENTER /ESC and some more are keys that will finish up the editing of the text! So we can't check for those keys in the while() loop, because it will be skipped by the LVN_ENDLABELEDIT message. So first thing we check is, if ESC was hit.

Using a boolean variable, we can determine whenever a cancel event has occurred, or we can actually process the new text.
Selecting (Highlighting) items/All Items

It is not really hard, we use this piece of code:

Select Item:
ListView_SetItemState(hList, -1, 0, LVIS_SELECTED); // deselect all items
SendMessage(hList,LVM_ENSUREVISIBLE ,
(WPARAM)item,FALSE); // if item is far, scroll to it
ListView_SetItemState(hList,item,
LVIS_SELECTED ,LVIS_SELECTED); // select item
ListView_SetItemState(hList,item,
LVIS_FOCUSED ,LVIS_FOCUSED); // optional

Select All Item:
ListView_SetItemState(hList, -1, 0,
LVIS_SELECTED); // deselect all items
ListView_SetItemState(hList,-1,
LVIS_SELECTED ,LVIS_SELECTED);

The -1 means ALL items. If you want an individual item, just write its index number.
Bugs and Help

It could be that you are using Win2000 for compiling this source code and you may happen to get into a few problems of linking or compiling..
Problem

The thread exits with code 0x0 (exit 0) // something like this :).

When you compile your exe and try to run it, it wont run. Nothing is loading..??? This is because we didn't link with the comctl32.lib. Add it to your link settings: Project -> Settings -> Link Tab -> general -> add comctl32.lib into "object/libary modules" -> click OK. Rebuild project.. and try to run. It's suppose to work. Another way of solving this is to add this code to your WinMain:

// add this code if win2000/nt/xp doesn't
// load ur winmain (experimental only)
// also add comctl32.lib

INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwICC = ICC_LISTVIEW_CLASSES;
InitCtrls.dwSize = sizeof(INITCOMMONCONTROLSEX);
BOOL bRet = InitCommonControlsEx(&InitCtrls);

It could work, I haven't tried it..

Yet another approach is directly via the: InitCommonControls(); function!! Write it in the case WM_INITDIALOG:
---F.A.Q---

Q. Why sometimes I get wrong result from the LVM_GETNEXTITEM when I select items out of the range?

A. This is because we have to set a flag called LVNI_SELECTED:

SendMessage(hList,LVM_GETNEXTITEM,-1,LVNI_FOCUSED|LVNI_SELECTED);

Q. How do I attach a popup menu when clicking on an item (right button...etc.)?

A. Really easy. First make the menu in the resource editor, and use this code:

HMENU hMenu = LoadMenu (NULL, MAKEINTRESOURCE (IDR_MENU));
HMENU hPopupMenu = GetSubMenu (hMenu, 0);
POINT pt;
SetMenuDefaultItem (hPopupMenu, -1, TRUE);
GetCursorPos (&pt);
SetForegroundWindow (hWnd);
TrackPopupMenu (hPopupMenu,
TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, NULL);
SetForegroundWindow (hWnd);
DestroyMenu (hPopupMenu);
DestroyMenu (hMenu);

Note: Make sure you pressed on an item, use a if(selected!=-1) or alike code. That way we prevent from showing the popup menu anytime/anywhere.

Q. How do I color the: entire text color, list view back color, text back color?

A. It is not really hard. We can do it like this:

int count=SendMessage(GetDlgItem(hWnd,
IDC_LIST),LVM_GETITEMCOUNT ,0,0);
SendMessage(GetDlgItem(hWnd,IDC_LIST),
LVM_SETTEXTCOLOR,0,(LPARAM)SetColor());
SendMessage(GetDlgItem(hWnd,IDC_LIST),
LVM_SETTEXTBKCOLOR,0,(LPARAM)SetColor());
SendMessage(GetDlgItem(hWnd,IDC_LIST),
LVM_SETBKCOLOR,0,(LPARAM)SetColor());
SendMessage(GetDlgItem(hWnd,IDC_LIST),
LVM_REDRAWITEMS ,0,count);
UpdateWindow(hWnd);
..
..
..
LONG SetColor()
{
static CHOOSECOLOR cc ;
static COLORREF crCustColors[16] ;

cc.lStructSize = sizeof (CHOOSECOLOR) ;
cc.hwndOwner = NULL ;
cc.hInstance = NULL ;
cc.rgbResult = RGB (0x80, 0x80, 0x80) ;
cc.lpCustColors = crCustColors ;
cc.Flags = CC_RGBINIT | CC_FULLOPEN ;
cc.lCustData = 0 ;
cc.lpfnHook = NULL ;
cc.lpTemplateName = NULL ;

ChooseColor (&cc);
return cc.rgbResult;
}

Note: However, this code has a really tiny bug. If the user presses the cancel/[X] from the color dialog, the default color of it will be returned, and colors your default list box color. However, it's not really hard to fix.

Q. How can I edit an item's caption using a button/menu...etc., just like renaming a folder with right click on mouse option?

A. Send the message: LVM_EDITLABEL to the list view. Example: SendMessage(hList,LVM_EDITLABEL ,(WPARAM)index,(LPARAM)0);.

We come to the end of this article. I hope everyone enjoyed this as much as it took me time to find and add a proper code ;-).
About Bengi

Win32API Rocks your world!!



版权所有!www.sieye.cn
E.Mail:sieye@sohu.com QQ:66697110