CListView

原文地址:http://www.360doc.com/content/11/0420/22/6605519_111135801.shtml


CListView 是 CView 是一个导出类,仅仅是一个视的管理器,不具备  CListCtrl 中的方法。但在类中嵌套了一个   CListCtrl   对象,因此当我们要对列表对象进行访问的话,就必须通过  GetListCtrl  来取得CListCtrl   对象后。
对CListCtrl   对象的操作有以下方法:
1.动态生成列表控件。
在资源页面里,点击列表控件,然后在需要放置列表的地方拖拉就可以生成一个列表,查看它的属性->样式,可以设置它的样式,例如图标,小图标,列表,报告,单个选择,自动排列,没有标签换行,编辑标签 等等。
这种方法是静态生成列表控件,程序运行时列表就已经生成了,如果我们需要动态生成列表控件,让列表在我需要他生成的时候才生成,就要用CListCtrl::Create函数。
BOOL  Create( DWORD  dwStyle , const RECT&  rect , CWnd*  pParentWnd , UINT  nID  );
其中参数dwStyle用来确定列表控制的风格;rect用来确定列表控制的大小和位置;pParentWnd用来确定列表控制的父窗口,通常是一个对话框;nID用来确定列表控制的标识。其中列表控制的风格可以是下列值的组合:
       LVS_ALIGNLEFT 用来确定表项的大小图标以左对齐方式显示; 

  LVS_ALIGNTOP 用来确定表项的大小图标以顶对齐方式显示; 

  LVS_AUTOARRANGE 用来确定表项的大小图标以自动排列方式显示; 

  LVS_EDITLABELS 设置表项文本可以编辑,父窗口必须设有LVN_ENDLABELEDIT风格; 

  LVS_ICON 用来确定大图标的显示方式; 

  LVS_LIST 用来确定列表方式显示; 

  LVS_NOCOLUMNHEADER 用来确定在详细资料方式时不显示列表头; 

  LVS_NOLABELWRAP 用来确定以单行方式显示图标的文本项; 

  LVS_NOSCROLL 用来屏蔽滚动条; 

  LVS_NOSORTHEADER 用来确定列表头不能用作按钮功能; 

  LVS_OWNERDRAWFIXED 在详细列表方式时允许自绘窗口; 

  LVS_REPORT 用来确定以详细资料即报告方式显示; 

  LVS_SHAREIMAGELISTS用来确定共享图像列表方式; 

  LVS_SHOWSELALWAYS 用来确定一直显示被选中表项方式; 

  LVS_SINGLESEL 用来确定在某一时刻只能有一项被选中; 

  LVS_SMALLICON 用来确定小图标显示方式; 

  LVS_SORTASCENDING 用来确定表项排序时是基于表项文本的升序方式; 

  LVS_SORTDESCENDING 用来确定表项排序时是基于表项文本的降序方式;
以上的风格其实就是静态生成列表控件时 “属性->样式” 标签里的内容。如果列表控件创建完之后我们需要修改他的样式,就可以用GetWindowLong来获取CListCtrl已有的样式,然后用SetWindowLong来修改样式。
2.设置扩展样式。
除了上面的哪些基本样式,CListctrl还有更多的扩展样式,如果我们要添加或删除扩展样式,就不是像基本样式那样用SetWindowLong了,CListctrl提供了一个函数CListCtrl::SetExtendedStyle,这个函数可以添加或删除扩展样式,相应的要获得CListctrl的扩展样式,应该使用CListCtrl::GetExtendedStyle,而不是GetWindowLong。
DWORD SetExtendedStyle( DWORD  dwNewStyle  );
DWORD  dwNewStyle 就是你要设置的样式咯,可以是以下值的组合(抄别人的不知道完不完整):
CListCtrl控件的扩展样式
LVS_EX_GRIDLINES //绘制表格线
LVS_EX_SUBITEMIMAGES//子项目图标列表
LVS_EX_CHECKBOXES //带复选框
LVS_EX_TRACKSELECT //自动换行
LVS_EX_HEADERDRAGDROP//报表头可以拖拽
LVS_EX_FULLROWSELECT //选择整行
LVS_EX_ONECLICKACTIVATE//单击激活
LVS_EX_TWOCLICKACTIVATE//双击激活
LVS_EX_FLATSB//扁平滚动条
LVS_EX_REGIONAL
LVS_EX_INFOTIP
LVS_EX_UNDERLINEHOT
LVS_EX_UNDERLINECOLD
LVS_EX_MULTIWORKAREAS//多工作区
3.添加列 与 删除列
列表控件创建好之后,就要为他添加列了,使用CListCtrl::InsertColumn函数为它添加列。要删除一列,使用CListCtrl::DeleteColumn函数。
int InsertColumn( int  nCol , LPCTSTR  lpszColumnHeading , int  nFormat  = LVCFMT_LEFT, int  nWidth  = -1, int nSubItem  = -1 );
iCol:为列的位置,从零开始 
lpszColumnHeading:为显示的列名 
nFormat:为显示对齐方式 
nWidth:为显示宽度 
nSubItem:为分配给该列的列索引。这个参数有什么作用至今不懂,希望懂的朋友告诉我。
例如:
InsertColumn(3,"所在区域",LVCFMT_LEFT,60);
即添加一个名字为“所在区域”的列,名字靠左排列,列的长度为60,这个列是第四列(从0开始算)。
4.添加行 与 删除行。
添加行即是添加记录咯,一行就是一条记录。使用CListCtrl::InsertItem函数来添加行。要删除一行,使用CListCtrl::DeleteItem函数,注意,如果你删除的行不是最后一行,那么删除后下面的行会自动上移填补删除行的空间。要删除全部行,使用CListCtrl::DeleteAllItems。
int InsertItem( int  nItem , LPCTSTR  lpszItem  );
nItem  :行的索引,从0开始计算。
lpszItem  :行的名称,一般为0或者""(即不需要为行添加名称)。如果为他赋值一个字符串,实际效果是该字符串 会成为该行的第一列的内容。
例如 InsertItem( 5, 0) 即在行索引5的位置添加一行(即是第六行)。
5.设置某行某列的内容 与 获取某行某列的内容。
如果要设置某行某列的内容。使用CListCtrl::SetItemText函数。
BOOL SetItemText( int  nItem , int  nSubItem , LPTSTR  lpszText  );
nItem        :行的索引,从0开始。
nSubItem  :列的索引,从0开始。
lpszText    :要填充的内容。
例如  SetItemText(6,3,"hello");  即设置第7行,第4列的内容为“hello”。
 
相反地,如果我们要提取某行某列的内容,使用CListCtrl::GetItemText函数。
CString GetItemText( int  nItem , int  nSubItem  ) const;
nItem        :行的索引,从0开始。
nSubItem  :列的索引,从0开始。
例如 CString text;    text=GetItemText(6,3);    提取第7行,第4列的内容并赋给text。
6.获取一列的属性和设置一列的属性。
如果要获取某列的属性,譬如说某列的列名、列的长度。就要用CListCtrl::GetColumn函数。
BOOL GetColumn( int  nCol , LVCOLUMN*  pColumn  ) const;
  nCol                :列的索引,从0开始。
  pColumn         :指向LVCOLUMN结构体的指针。该LVCOLUMN结构体就是用来保存得到的列属性信息的。
LVCOLUMN结构体有一个成员变量 mask ,你必须为这个变量赋值,用来指定你要获取什么属性,譬如说你要获取某列的长度,你就必须为mask赋值LVCF_WIDTH,如果你要获取某列的列名,就必须为mask赋值LVCF_TEXT。从MSDN中还有这样的描述:If the  mask member specifies the  LVCF_TEXT value, the  pszText member must contain the address of the buffer that receives the item text and the  cchTextMax member must specify the size of the buffer.
意思是说如果你要获取的属性是列名,就必须为LVCOLUMN结构体的pszText变量指定一个buf的地址,这个buf用来接收列名,还要为LVCOLUMN结构体的cchTextMax变量指定这个buf的大小。
下面的例子表示要获取第3列的列名和列长度:
  LVCOLUMN lvc;
  lvc.mask=LVCF_WIDTH | LVCF_TEXT;
  char name[20];
  lvc.pszText=name;
  lvc.cchTextMax=20;
  m_listctrl.GetColumn(2,&lvc);
成功获取属性信息的话长度就保存在LVCOLUMN lvc的cx变量里,列名就保存在char name[20]里,当然你也可以从LVCOLUMN lvc的pszText变量里得到列名,因为pszText变量保存了char name[20]的首地址。
如果要设置某列的属性,就要用CListCtrl::SetColumn函数。
BOOL SetColumn( int  nCol , const LVCOLUMN*  pColumn  );
参数意思和GetColumn一样,而SetColumn里的 pColumn 参数保存了你将要设置的属性。下面的例子表示把第四列的列长度设置为50,列名设置为“销量”:
LVCOLUMN lvc;
lvc.mask=LVCF_WIDTH | LVCF_TEXT;    //同样的,你要设置什么属性,就要先给mask变量赋值响应的标志。
lvc.pszText="销量";
lvc.cx=50;
m_listctrl.SetColumn(3,&lvc);
CListCtrl控件生成之后,无论你单击还是双击它的单元格,都没有任何反应,如果我们需要像EXCEL那样双击一个单元格后即可编辑该单元格的内容,那怎么办呢?一个方法就是双击单元格之后,在该单元格的地方生成一个编辑框,然后让输入焦点落在该编辑框上,我们即可对该编辑框进行文字编辑,当输入焦点从编辑框上消失时(通常是用户在编辑框外的地方单击,就像EXCEL当编辑完一个单元格的内容后用户在单元格外的地方单击一下以示确认输入完毕一样。),就理解为编辑完毕,然后把编辑框的内容设置为该单元格的内容,最后隐藏或销毁编辑框。
 
1.从CListCtrl类继承,新建一个自己的列表框类,在这里我取名为CPage2ListCtrl,下面就要在CPage2ListCtrl 类实现双击单元格编辑功能。上面也已经说道,双击单元格后要显示一个编辑框,我们把那个编辑框看做是CPage2ListCtrl 类的成员。接下来就要为这个编辑框新建一个从CEdit继承的类,为什么不直接用CEdit类呢,以后就知道为什么的了。这个从CEdit继承的编辑框类我取名为CListEdit。
 
2.为CPage2ListCtrl类添加成员变量和函数:
public:
 void DisposeEdit();     //这个函数的作用是.....往下看就知道的了。
 CListEdit m_edit;       //这个就是双击单元格后显示的编辑框。
 int row,col;                 //用来记录双击的单元格是第几行第几列。
 
3.重写CPage2ListCtrl类的PreSubclassWindow()虚函数,这个函数的作用是什么,这里就不说了,我们要在这个函数里设置CPage2ListCtrl类的基本样式和扩展样式,还有创建m_edit成员变量的编辑框。
void CPage2ListCtrl::PreSubclassWindow() 
{
 // TODO: Add your specialized code here and/or call the base class
 ModifyStyle(LVS_EDITLABELS,0);
 ModifyStyle(0,LVS_REPORT);
 ModifyStyle(0,LVS_SHOWSELALWAYS);
 SetExtendedStyle(LVS_EX_FLATSB  |  LVS_EX_FULLROWSELECT   |  LVS_EX_GRIDLINES);
 m_edit.Create(WS_CHILD|WS_CLIPSIBLINGS|WS_EX_TOOLWINDOW|WS_BORDER,
                        CRect(0,40,10,50),this,1001);
 CListCtrl::PreSubclassWindow();
}
 
4.对CPage2ListCtrl类添加消息处理,对双击事件进行响应,消息名称是WM_LBUTTONDBLCLK。
void CPage2ListCtrl::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
 // TODO: Add your message handler code here and/or call default
 CListCtrl::OnLButtonDblClk(nFlags, point);
 LVHITTESTINFO info;
 info.pt=point;
 info.flags=LVHT_ONITEMLABEL;
 if(SubItemHitTest(&info)>=0)      //SubItemHitTest进行点击测试,目的是确定双击的单元格是第几行第几列。
 {
  row=info.iItem;                           //行索引
  col=info.iSubItem;                      //列索引
  CRect rect;
  GetSubItemRect(row,col,LVIR_LABEL,rect);     //得到该单元格矩形的位置大小信息。
  CString str;
  str = GetItemText(row,col);                                //获取该单元格已存在的文本内容。
  m_edit.MoveWindow(rect);                               //把编辑框移动到该单元格矩形上。
  m_edit.SetWindowText(str);                              //把单元格原本的内容显示到编辑框上。
  m_edit.ShowWindow(SW_SHOW);                   //显示编辑框。
  m_edit.SetSel(0,-1);                                          //全选编辑框的内容。
  m_edit.SetFocus();                                            //设置输入焦点在编辑框上。
     UpdateWindow();
 }
}
 
5.当编辑框输入焦点消失时,就认为编辑完毕,然后把编辑框的内容设置到单元格上。编辑框输入焦点消失时会产生WM_KILLFOCUS消息,我们对该消息进行响应,这就是为什么上面要为编辑框新建一个类的原因了。
void CListEdit::OnKillFocus(CWnd* pNewWnd) 
{
 CEdit::OnKillFocus(pNewWnd);
 CPage2ListCtrl * temp;     
//这里新建了一个CPage2ListCtrl 类型的指针,所以在CListEdit的cpp文件前必须先添加#include "Page2ListCtrl.h"
 temp=(CPage2ListCtrl *)GetParent();  
 temp->DisposeEdit();     //调用父窗口的DisposeEdit()函数。
 // TODO: Add your message handler code here 
}
 
void CPage2ListCtrl::DisposeEdit()
{ CString sLabel;
  m_edit.GetWindowText(sLabel);
   this->SetItemText(row,col,sLabel);
  m_edit.ShowWindow(SW_HIDE);
  if(GetItemCount() == row+1)      //如果编辑的这行是最后一行的话,就添加一行,在这里可以设置更多的判断。
   InsertItem(row+1,0);
  return ;
}
下面要做一个CListCtrl控件,当用户点击某列标题的时候,就根据该列数据进行全部记录的升序或降序排列。步骤如下:
1.从CListCtrl类继承,新建一个新的列表控件类,这里我把它命名为CSortListCtrl。
 
2.为CSortListCtrl类添加以下成员变量:
  BOOL m_fAsc;                  //用来设置是升序排列还是降序排列
  int m_nSortedCol;             //用来记录被点击的是哪一列
 
3.在CSortListCtrl类的构造函数中添加m_fAsc=TRUE;把排序初始化为按升序排序。
 
4.当列标题被点击的时候,会促发LVN_COLUMNCLICK消息,因此对CSortListCtrl类的LVN_COLUMNCLICK消息进行消息响应,进行排序操作。
消息响应函数如下:
void CSortListCtrl::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult) 
{
 NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
 // TODO: Add your control notification handler code here
 for (int index=0;index   SetItemData(index,index); 
//SetItemData函数的作用是为一行设置一个标签,以后通过该标签就能找回这一行,标签是一个数值。SetItemData(index,index)意思就是这一行是第几列就把标签设为几。为什么要设置标签呢?以后的排序必须用到标签。
 
 if( pNMListView->iSubItem == m_nSortedCol )     //如果被点击的列和上一次点击的列是同一列的话
  m_fAsc = !m_fAsc;                             //排序方式取反,即上次是升序这次就为降序,上次降序这次就为升序
 else                                                   //如果被点击的列和上一次点击的列不是同一列的话
 {
  m_fAsc = TRUE;                               //排序方式为升序
  m_nSortedCol = pNMListView->iSubItem;        //把这次点击的列序号保存到m_nSortedCol 变量中
 }
 SortItems( ListCompare, (DWORD)this );    
//SortItems函数进行排序操作,ListCompare是回调函数,SortItems函数会调用此回调函数进行排序判断

 *pResult = 0;
}
 
以下为回调函数的实现,回调函数必须为全局函数。
int CALLBACK ListCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
 CSortListCtrl* pV=(CSortListCtrl*)lParamSort;
 
CString Comp1NAME,Comp2NAME,Comp1SUM,Comp2SUM;
int SUM1,SUM2;
int iCompRes;

 switch(pV->m_nSortedCol)   //判断被点击的是哪一列
 {
 case(0):                                //如果被点击的是第一列(假设第一列是字符型数据)
  {
  for(int i=0;iGetItemCount();i++)
  { 
   if (  lParam1==pV->GetItemData(i))         //寻找标签为参数lParam1的哪一行
     Comp1NAME=pV->GetItemText(i,0);     //把这行第一列的内容提取出来
    
   if ( lParam2==pV->GetItemData(i))         //寻找标签为参数lParam2的哪一行
     Comp2NAME=pV->GetItemText(i,0);     //把这行第一列的内容提取出来
  }
  iCompRes=Comp1NAME.Compare(Comp2NAME);    //把内容进行比较,结果保存到iCompRes上
  break;
  }

  case(1):                                                  //如果被点击的是第二列(假设第二列是整型数据)
  {
  for(int i=0;iGetItemCount();i++)
  { 
   if (  lParam1==pV->GetItemData(i))
   {Comp1SUM=pV->GetItemText(i,1);
    SUM1=atoi(Comp1SUM);
   } 
   if ( lParam2==pV->GetItemData(i))
   {Comp2SUM=pV->GetItemText(i,1);
    SUM2=atoi(Comp2SUM);
   }
  }
  
  if(SUM1 == SUM2) 
   iCompRes = 0;
//标签为lParam1行第一列的数据SUM1等于标签为lParam2行第一列的数据SUM2则返回0
  else
   iCompRes=(SUM1 < SUM2)?-1:1;            
//标签为lParam1行第一列的数据SUM1小于标签为lParam2行第一列的数据SUM2则返回-1,大于则返回1
  break;
  }
  default:  
  break;
 }
 
 //根据当前的排序方式进行调整,返回应当如何排序。函数返回-1代表第一项排应在第二项前面,返回1代表第一项排应在第二项后面,返回0代表两项相等。
 if(pV->m_fAsc)        //升序排列
  return iCompRes;  //switch里面的比较就是按照升序排列来返回结果
 else                         //降序排列
  return iCompRes*-1;
}
注意,在以上的例子中我只假设了列表控件只有两列而且点击这两列都能进行排序,如果你的列表有多列,而点击某些列你又不想激活排序的话,就必须在void CSortListCtrl::OnColumnclick函数开头进行判断,判断出如果点击的列是不想激活排序的列的话就马上让函数return掉,不执行往后的排序操作。另外需要激活排序的列必须都能在回调函数里的switch判断中找到该列的入口,否则的话就会从default跳出,没做任何比较就返回函数结果,排序就会不正确。
 
 
 
CListCtrl控件的标题栏默认是灰色的,如果和旁边的颜色不搭配,想要修改它的颜色,如何做呢?下面给出了实现方法。
1.从CHeaderCtrl继承,新建一个类,在这里我把新建的这个类命名为CColorHeaderCtrl。从名字来看,CHeaderCtrl类应该是用来控制标题的,除了能控制列表的标题以外,还能不能控制其他东西的标题我就没深入研究过了。
 
2.对CColorHeaderCtrl类的WM_PAINT消息进行消息响应,在响应函数里进行标题的重绘,响应函数的实现如下:
void CColorHeaderCtrl::OnPaint() 
{
 CPaintDC dc(this); // device context for painting
 
 // TODO: Add your message handler code here
 
 // Do not call CHeaderCtrl::OnPaint() for painting messages
    CRect rect;
 GetClientRect(rect);
 dc.FillSolidRect(rect,RGB(147,212,255));   //重绘标题栏颜色
 int nItems = GetItemCount();
    CRect rectItem;
 CPen m_pen(PS_SOLID,1,RGB(211,211,211));      //分隔线颜色
 CPen * pOldPen=dc.SelectObject(&m_pen);
 CFont m_font;
 m_font.CreatePointFont(90,"宋体");        //字体
 CFont * pOldFont=dc.SelectObject(&m_font);
 dc.SetTextColor(RGB(13,141,237));     //字体颜色
 for(int i = 0; i  {  
  GetItemRect(i, &rectItem);
     rectItem.top+=2;
     rectItem.bottom+=2; 
     dc.MoveTo(rectItem.right,rect.top);                //重绘分隔栏
     dc.LineTo(rectItem.right,rectItem.bottom);
  TCHAR buf[256];
  HD_ITEM hditem;
  
  hditem.mask = HDI_TEXT | HDI_FORMAT | HDI_ORDER;
  hditem.pszText = buf;
  hditem.cchTextMax = 255;
  GetItem( i, &hditem );                                       //获取当然列的文字
  UINT uFormat = DT_SINGLELINE | DT_NOPREFIX | DT_TOP |DT_CENTER | DT_END_ELLIPSIS ;
  dc.DrawText(buf, &rectItem, uFormat);           //重绘标题栏的文字
 }
 dc.SelectObject(pOldPen);
 dc.SelectObject(pOldFont);
}
 
3.能重绘标题的CColorHeaderCtrl类已经设计好了,接下来如何应用到列表控件上呢?首先要从CListCtrl继承,新建一个列表控件类,这里我为新建的列表控件类命名为CMyListCtrl,接下来为CMyListCtrl类添加成员变量:
 CColorHeaderCtrl m_colorheader;
没错,就是上面设计的类型为CColorHeaderCtrl的成员变量。接下来,为你想要重绘标题栏的列表控件关联一个成员变量,变量类型为CMyListCtrl,这里我取变量名为m_colorlistctrl。最后在合适的地方,譬如说包含列表控件的父窗口的OnInitDialog()函数里添加以下代码:
 CHeaderCtrl * m_head=m_colorlistctrl.GetHeaderCtrl();
 m_colorlistctrl.m_colorheader.SubclassWindow(m_head->GetSafeHwnd());
 
运行程序,你会发现列表控件的标题栏被重绘了。

你可能感兴趣的:(MFC,CView,MFC)