2.6 列表视图控件典型实例

列表视图控件可以用来浏览数据,也可以用来显示图标,在开发应用程序时经常要使用列表视图控件,可以用它来代替DataGrid数据控件来浏览数据,本节通过几个实例介绍列表视图控件的应用。

实例059 Windows资源管理器

本实例可以方便操作、提高效率

实例位置:光盘\mingrisoft\02\059

实例说明

在Windows的资源管理器中,使用了列表视图控件来显示文件和目录信息。在该列表视图控件中显示了当前目录下的所有子目录和文件,用户可以以文件名、文件日期等信息进行排序,并且双击某个文件时会调用系统关联的程序打开文件。本实例通过列表视图控件设计Windows资源管理器,实例运行结果如图2.20所示。

图2.20 Windows资源管理器

技术要点

为了能够在列表视图中显示文件和目录,在列表视图控件中定义了一个字符串变量m_CurDir,表示列表视图控件当前显示的目录文件。然后定义一个DisplayPath函数,根据参数描述的目录,列举该目录下的文件和目录,将其显示在列表视图中。

实现过程

(1)新建一个基于对话框的应用程序。

(2)从CHeaderCtrl派生一个子类CFileHeaderCtrl,从CListCtrl类派生一个子类CFileListCtrl。

(3)在对话框上添加一个图片控件和一个列表视图控件,设置图片控件ID属性为IDC_FRAME;设置列表视图控件View属性为Report;添加列表视图控件,分别为控件添加成员变量m_Frame和m_FileList。

(4)向CFileListCtrl类中添加成员变量。代码如下:

        CString m_BaseDir;                      //基目录
        CString m_CurDir;                       //记录当前列表中文件的目录
        CFileHeaderCtrl m_ctlHeader;            //列头
        int m_nNumColumns;                      //列数
        int m_nSortColumn;                      //排序列
        BOOL m_bAscend;                         //是否升序排列
        CImageList m_ImageList;                 //定义图像列表

(5)向CFileListCtrl类中添加SetColumns方法,为视图列表添加列。代码如下:

        BOOL CFileListCtrl::SetColumns(const CString &strHeadings)
        {
            int nStart = 0;
            for( ;; )
            {
                int nComma=strHeadings.Find(_T(','),nStart);                  //截取字符串
                if( nComma == -1 )
                    break;
                CString strHeading=strHeadings.Mid(nStart,nComma-nStart);     //获取文本
                nStart=nComma+1;                                             //略过","
                int nSemiColon = strHeadings.Find( _T(';'), nStart );
                if( nSemiColon == -1 )
                    nSemiColon = strHeadings.GetLength();
                int nWidth=atoi(strHeadings.Mid(nStart,nSemiColon-nStart));   //获取宽度
                nStart=nSemiColon+1;                                         //指向下一列信息
                if( InsertColumn( m_nNumColumns++, strHeading, LVCFMT_LEFT, nWidth ) == -1 )
                    return FALSE;                                             //插入列
            }
            return TRUE;
        }

(6)向CFileListCtrl中添加AddItem方法,向视图列表中添加视图项,并且为视图项关联数据(当前行所有列的文本)。代码如下:

        int CFileListCtrl::AddItem(LPCTSTR pszText, ... )
        {
            int nIndex=InsertItem(GetItemCount(),pszText);                   //添加行,返回行索引
            LPTSTR*pszColumnTexts=new LPTSTR[m_nNumColumns];                 //记录各列文本
            pszColumnTexts[ 0 ] = new TCHAR[ lstrlen( pszText ) + 1 ];
            lstrcpy(pszColumnTexts[0],pszText);                             //复制第一列文本到pszColumnTexts中
            va_list list;
            va_start( list, pszText );
            //设置列文本
            for(int nColumn=1;nColumn<m_nNumColumns;nColumn++)               //将各列文本复制到pszColumnTexts中
            {
                pszText = va_arg( list, LPCTSTR );
                CListCtrl::SetItem( nIndex, nColumn, LVIF_TEXT, pszText, 0, 0, 0, 0 );
                pszColumnTexts[ nColumn ] = new TCHAR[ lstrlen( pszText ) + 1 ];
                lstrcpy( pszColumnTexts[ nColumn ], pszText );
            }
            va_end(list);
            SetItemDataList(nIndex,pszColumnTexts);                         //设置视图项数据
            return nIndex;
        }

(7)向CFileListCtrl中添加SetItemDataList方法,设置视图项关联的数据。代码如下:

        BOOL CFileListCtrl::SetItemDataList(int iItem, LPTSTR *pchTexts)
        {
            if (CListCtrl::GetItemData(iItem) == NULL)
            {
                CItemData*pItemData=new CItemData();                          //创建一个CItemData对象
                pItemData->m_ColumnTexts=pchTexts;                           //设置列文本
                return CListCtrl::SetItemData(iItem,(DWORD)pItemData);        //设置项目数据
            }
        }

(8)向CFileListCtrl中添加LoadSysFileIcon方法,加载系统文件图像列表。代码如下:

        void CFileListCtrl::LoadSysFileIcon()
        {
            SHFILEINFO shInfo;                                            //定义外壳文件信息
            memset(&shInfo,0,sizeof(SHFILEINFO));
            HIMAGELIST hImage = (HIMAGELIST)SHGetFileInfo("C:\\",0,&shInfo, sizeof( SHFILEINFO ),
                  SHGFI_SYSICONINDEX|SHGFI_SMALLICON);                   //加载系统文件图像列表
            m_ImageList.Attach(hImage);                                  //附加图像列表句柄
            SetImageList(&m_ImageList,LVSIL_SMALL);                      //设置列表视图关联的图像列表控件
        }

(9)向CFileListCtrl类中添加DisplayPath方法,显示指定目录下的子目录和文件到列表中。代码如下:

            void CFileListCtrl::DisplayPath(LPCTSTR lpPath)
            {
            DeleteAllItems();                            //删除列表视图中的所有视图项
            BOOL bFind;
            CFileFind flFind;                             //定义文件查找对象
            CString csPath = lpPath;
            m_CurDir = lpPath;
            if ( csPath.Right(1) != "\\" )
            {
                    csPath+="\\";
                    m_CurDir+="\\";
            }
            csPath += "*.*";
            bFind=flFind.FindFile(csPath);               //开始查找文件
            CString csText,csFileSize,csDataTime;
            //设置列表当前显示的目录
            while ( bFind )
            {
                    bFind=flFind.FindNextFile();         //查找下一个文件
                    if(!flFind.IsDots()&&!flFind.IsHidden())//判断文件的属性
                    {
                        __int64 lFileLen=flFind.GetLength64();//获取文件长度
                        if(flFind.IsDirectory())         //是否是目录
                        {
                        csFileSize = "文件夹";
                        }
                        else
                        {
                        //格式化文件大小
                        double fGB = lFileLen /(double)(1024*1024*1024);
                        if (fGB < 1)
                        {
                              double fMB = lFileLen / (double)(1024*1024);
                              if (fMB < 1)
                              {
                                double fBK = lFileLen / (double)(1024);
                                if (fBK >1)
                                {
                                    csFileSize.Format("%2.2f KB",fBK);
                                }
                                else
                                {
                                    csFileSize.Format("%i B",lFileLen);
                                }
                              }
                              else
                              {
                                csFileSize.Format("%2.2f MB",fMB);
                              }
                        }
                        else
                        {
                              csFileSize.Format("%2.2f GB",fGB);
                        }
                        }
                        csText=flFind.GetFileName();                //获取文件名
                        CTime time;
                        flFind.GetCreationTime(time);               //获取文件创建时间
                        csDataTime=time.Format("%Y-%m-%d %H:%M");
                        //将文件名,文件大小,文件创建时间添加到列表视图中
                        int nItem=AddItem(csText,csFileSize,csDataTime);
                        SHFILEINFO shInfo;                           //设置文件显示的图标
                        int nIcon=0;
                        SHGetFileInfo(flFind.GetFilePath(),0,&shInfo,sizeof(shInfo),SHGFI_ICON|SHGFI_SMALLICON);
                    DestroyIcon(shInfo.hIcon);
                    nIcon = shInfo.iIcon;
                    SetItem(nItem,0,LVIF_IMAGE,"",nIcon,0,0,0);   //显示文件关联的图标
                    if(flFind.IsDirectory())                      //设置项目标记,0表示文件,1表示目录
                    {
                        SetItemData(nItem,1);                     //设置视图项关联的数据
                    }
                    else
                    {
                        SetItemData(nItem, 0);
                    }
                    nItem++;
                }
            }
        }

举一反三

根据本实例,读者可以:

将数据表中数据添加到列表视图控件。

实例060 利用列表视图控件浏览数据

本实例可以方便操作、提高效率

实例位置:光盘\mingrisoft\02\060

实例说明

在开发应用程序时经常使用列表控件来显示二维数据。本实例实现了用列表控件显示数据表中数据的功能。运行程序,在列表控件中显示Access数据库mrdb.mdb的info表中的数据。实例运行结果如图2.21所示。

图2.21 利用列表视图控件浏览数据

技术要点

本实例主要通过ADO技术来连接数据库,通过CListCtrl类的SetItemText方法将数据显示在列表控件中。在使用SetItemText方法前,要先通过InsertColumn方法添加列,通过InsertItem方法来添加行,否则直接调用SetItemText方法将出错,SetItemText方法的语法如下:

BOOL SetItemText( int nItem, int nSubItem, LPTSTR lpszText );

参数说明:

● nItem:行索引。

● nSubItem:列索引。

● lpszText:字符串指针。

实现过程

(1)新建一个基于对话框的应用程序。

(2)在对话框上添加列表视图控件,设置ID属性为IDC_DATALIST,添加成员变量m_datalist。

(3)在StdAfx.h文件中导入ADO动态链接库。

(4)主要程序代码如下:

            BOOL CAddDateToListDlg::OnInitDialog()
            {
            CDialog::OnInitDialog();
            //…此处代码省略
            //设置列表视图控件的风格
            m_datalist.ModifyStyle(0L,LVS_REPORT);
            m_datalist.ModifyStyle(0L,LVS_SINGLESEL);
            m_datalist.ModifyStyle(0L,LVS_SHOWSELALWAYS);
            m_datalist.ModifyStyle(0L,LVS_NOSORTHEADER);
            m_datalist.SetExtendedStyle(LVS_EX_GRIDLINES);
            //设置列标题
            m_datalist.InsertColumn(0,"姓名");
            m_datalist.InsertColumn(1,"性别");
            m_datalist.InsertColumn(2,"出生日期");
            m_datalist.InsertColumn(3,"工作单位");
            m_datalist.InsertColumn(4,"移动电话");
            m_datalist.InsertColumn(4,"固定电话");
            //设置列宽度
            m_datalist.SetColumnWidth(0,100);
            m_datalist.SetColumnWidth(1,50);
            m_datalist.SetColumnWidth(2,100);
            m_datalist.SetColumnWidth(3,100);
            m_datalist.SetColumnWidth(4,100);
            m_datalist.SetColumnWidth(5,100);
            ::CoInitialize(NULL);                                            //初始化COM环境
            m_pConnection=NULL;
            m_pConnection.CreateInstance(__uuidof(Connection));              //连接对象实例化
            m_pConnection->ConnectionString="uid=;pwd=;DRIVER=
            {Microsoft Access Driver(*.mdb)};DBQ=mrdb.mdb;";                   //设置连接字符串
            m_pConnection->Open(L"",L"",L"",adCmdUnspecified);               //打开数据库
            _bstr_t bstrSQL="select*from info";                                //设置查询语句
            m_pRecordset=m_pConnection->Execute(bstrSQL,NULL,adCmdText);     //执行查询语句
            int i=0;
            while(!m_pRecordset->adoEOF)
            {
            //获得数据库中数据
            xm=(char*)(_bstr_t)m_pRecordset->GetCollect("xm");
            xb=(char*)(_bstr_t)m_pRecordset->GetCollect("xb");
            csrq=(char*)(_bstr_t)m_pRecordset->GetCollect("csrq");
            gzdw=(char*)(_bstr_t)m_pRecordset->GetCollect("gzdw");
            yddh=(char*)(_bstr_t)m_pRecordset->GetCollect("yddh");
            gddh=(char*)(_bstr_t)m_pRecordset->GetCollect("gddh");
            m_datalist.InsertItem(i,"");                                     //向列表视图控件中插入行
            //为每一行插入列
            m_datalist.SetItemText(i,0,xm);
            m_datalist.SetItemText(i,1,xb);
            m_datalist.SetItemText(i,2,csrq);
            m_datalist.SetItemText(i,3,gzdw);
            m_datalist.SetItemText(i,4,yddh);
            m_datalist.SetItemText(i,5,gddh);
            i+=1;
            m_pRecordset->MoveNext();                                        //向下移动记录集指针
            }
            m_pRecordset->Close();
            m_pConnection->Close();
            m_pRecordset=NULL;
            m_pConnection=NULL;
            ::CoUninitialize();
            return TRUE;
            }

举一反三

根据本实例,读者可以:

利用INI文件向列表控件中添加数据;

利用有规律的文本文件向列表控件中添加数据。

实例061 利用列表视图控件制作导航界面

本实例可以美化界面、简化操作

实例位置:光盘\mingrisoft\02\061

实例说明

列表控件能够响应鼠标的双击事件,在设计程序时,如果利用列表控件制作导航界面,会使程序更具有特色。运行程序,双击列表中的项,列表项所对应的复选框就会被选中,实例运行结果如图2.22所示。

图2.22 利用列表视图控件制作导航界面

技术要点

本实例主要通过处理列表控件的NM_DBLCLK消息来完成,如果用户双击列表控件后,NM_DBLCLK消息就会产生,通过GetSelectionMark方法可以获得列表所选项的索引,通过返回的索引值的不同可以进行相应的处理,如果返回的索引值为-1,表示没有选中列表项。

实现过程

(1)新建一个基于对话框的应用程序。

(2)在对话框上添加列表视图控件,设置ID属性为IDC_IMAGELST,添加成员变量m_mylist;添加12个复选框控件。

(3)向工程中添加7个图标资源,并修改图标ID。

(4)在对话框初始化时实现对列表框中数据的初始化,代码如下:

        BOOL CListNaviDlg::OnInitDialog()
        {
        CDialog::OnInitDialog();
        //…此处代码省略
        m_imglst.Create(32,32,ILC_COLOR32|ILC_MASK,0,0);  //创建图像列表
        //加载图标资源
        m_imglst.Add(::AfxGetApp()->LoadIcon(IDI_CK));
        m_imglst.Add(::AfxGetApp()->LoadIcon(IDI_CP));
        m_imglst.Add(::AfxGetApp()->LoadIcon(IDI_DB));
        m_imglst.Add(::AfxGetApp()->LoadIcon(IDI_DY));
        m_imglst.Add(::AfxGetApp()->LoadIcon(IDI_KC));
        m_imglst.Add(::AfxGetApp()->LoadIcon(IDI_PD));
        m_imglst.Add(::AfxGetApp()->LoadIcon(IDI_RK));
        m_mylist.SetImageList(&m_imglst,TVSIL_NORMAL);    //关联图像列表
        //向列表视图控件中插入列表项
        m_mylist.InsertItem(0,"项目1",0);
        m_mylist.InsertItem(1,"项目2",1);
        m_mylist.InsertItem(2,"项目3",2);
        m_mylist.InsertItem(3,"项目4",3);
        m_mylist.InsertItem(4,"项目5",4);
        m_mylist.InsertItem(5,"项目6",5);
        m_mylist.InsertItem(6,"项目7",6);
        return TRUE;
        }

(5)在OnDblclkImagelst中完成通过双击列表项实现某些功能,代码如下:

        void CListNaviDlg::OnDblclkImagelst(NMHDR* pNMHDR, LRESULT* pResult)
        {
        int cursel=m_mylist.GetSelectionMark();        //获得选中的列表项索引
        if(cursel==-1)return;
        //根据双击列表中不同的项目选中复选框
            switch(cursel)
            {
            case 0:
            {
            CButton *p=(CButton*)GetDlgItem(IDC_ADD);
            p->SetCheck(1);
            break;
            }
            case 1:
            {
            CButton *p=(CButton*)GetDlgItem(IDC_RELATINFO);
            p->SetCheck(1);
            break;
            }
            …//此处代码省略
            }
            *pResult = 0;
        }

举一反三

根据本实例,读者可以:

利用列表视图开发导航界面。

实例062 在列表视图中拖动视图项

本实例是一个提高效率、人性化的程序

实例位置:光盘\mingrisoft\02\062

实例说明

Windows系统是支持拖曳操作的系统,拖曳操作可以简化用户的操作步骤,一个拖曳操作可以实现剪切和复制两个操作。本实例实现了在列表视图控件中拖曳数据项的功能。运行程序,可以将列表中的任意行数据进行拖曳操作,实例运行结果如图2.23所示。

图2.23 在列表视图中拖动视图项

技术要点

本实例的实现主要是处理列表视图的LVN_BEG INDRAG消息。LVN_BEGINDRAG消息在有拖曳动作发生时产生。在实现列表项的拖动功能时,首先需要调用CreateDragImage方法创建一个拖动的图像列表(CImageList),然后调用图像列表的DragEnter方法锁定窗口更新,在拖动过程中显示图像。接着在鼠标移动的过程中调用图像列表的DragMove方法移动图像,显示拖动的效果。

实现过程

(1)新建一个基于对话框的应用程序。

(2)以CListCtrl类为基类派生一个CDragList类。

(3)向对话框中添加一个列表视图控件,为控件添加一个CDragList类的成员变量m_Grid。

(4)在CDragList类的头文件中声明变量,代码如下:

            int            m_ItmIndex;        //拖动项索引
            CImageList*    m_pDrgImg;         //拖动图像列表
            BOOL    m_Drag;                   //是否拖动

(5)在CDragList类中添加OnBegindrag方法,在开始拖动时调用,代码如下:

        void CDragList::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
        {
            HD_NOTIFY * phdn = (HD_NOTIFY *) pNMHDR;
            POINT pt;
            m_ItmIndex=((NM_LISTVIEW*)pNMHDR)->iItem; //获得当前拖动项索引
            m_pDrgImg  =CreateDragImage(m_ItmIndex,&pt);   //创建拖动图像列表
            m_pDrgImg->BeginDrag(0,pt);                  //开始拖动
            ClientToScreen(&pt);                         //转换客户坐标到屏幕坐标
            m_pDrgImg->DragEnter(this,pt);                 //锁定窗口更新
            m_Drag=TRUE;                            //标记拖动
            *pResult = 0;
        }

(6)在CDragList类的消息映射部分添加映射宏,在用户拖动节点时执行OnBegindrag方法,代码如下:

ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBegindrag)

(7)处理CDragList类的WM_MOUSEMOVE消息,在该消息的处理函数中随着鼠标的移动位置更新拖动图像的位置,代码如下:

        void CDragList::OnMouseMove(UINT nFlags, CPoint point)
        {
            if(m_Drag)                            //如果拖动
            {
                CPoint pt;
                pt.x = point.x;
                pt.y=point.y+(m_ItmIndex+1)*15;  //设置移动位置
                m_pDrgImg->DragMove(pt);         //移动图像
            }
            CListCtrl::OnMouseMove(nFlags, point);
        }

(8)处理CDragList类的WM_LBUTTONUP消息,在该消息的处理函数中结束拖动,并将拖动的数据添加到视图中,代码如下:

        void CDragList::OnLButtonUp(UINT nFlags, CPoint point)
        {
            if(m_Drag)                                         //如果拖动
            {
                m_pDrgImg->EndDrag();                          //结束拖动
                m_Drag=FALSE;                                  //标记为不拖动
                char name[256];
                LV_ITEM lvi;
                CString subitem[3];
                for(int i=2;i>=0;i--)
                {
                    ZeroMemory(&lvi,sizeof(LV_ITEM));          //整理内存控件
                    lvi.iItem       =m_ItmIndex;               //设置行
                    lvi.iSubItem    =i;                        //设置列
                    lvi.mask        =LVIF_IMAGE|LVIF_TEXT; //标志
                    lvi.pszText     =name;                     //列表项内容
                    lvi.cchTextMax  =255;                      //文本最大值
                    GetItem(&lvi);                             //获得节点
                    subitem[i].Format("%s",name);              //设置显示文本
                }
                //插入列表项
                InsertItem(&lvi);
                SetItemText(m_ItmIndex,1,subitem[1]);
                SetItemText(m_ItmIndex,2,subitem[2]);
            }
            CListCtrl::OnLButtonUp(nFlags, point);
        }

举一反三

根据本实例,读者可以:

在文本框之间实现文本的拖动。

实例063 有排序功能的列表视图控件

这是一个可以提高基础技能的实例

实例位置:光盘\mingrisoft\02\063

实例说明

列表控件在默认情况下不会对单击列标题产生任何动作。本实例实现了利用列标题对列表视图进行数据排序的功能。运行程序,单击列表控件列标题后,程序将对该列标题所在列的数据进行排序,如图2.24所示。

图2.24 具有排序功能的列表视图控件

技术要点

对列表视图进行排序是通过两部分进行的。第一部分是定义一个表头控件,实现排序箭头的绘制,第二部分是通过自定义的SortFunction方法实现对视图列排序。

为了能够在视图列排序时在表头部分显示一个箭头标记表示当前的排列方式,需要自定义一个表头控件,根据升序或降序排列绘制不同方向的箭头标记。在列表视图控件中,表头部分是一个CHeaderCtrl控件,为了自定义表头,可以从CHeaderCtrl派生一个子类,然后改写DrawItem虚方法,根据不同的排列方式绘制相应的箭头符号。最后改写列表视图控件的PreSubclassWindow虚方法,在该方法中将自定义的表头控件子类化,使其关联到列表视图控件的表头控件上,这样就实现了列表视图控件表头部分的绘制。

        void CListHeaderCtrl::PreSubclassWindow()
        {
            ASSERT(GetStyle()&LVS_REPORT);                          //判断列表视图的风格是否是表格形式
            CListCtrl::PreSubclassWindow();
            VERIFY( m_ctlHeader.SubclassWindow( GetHeaderCtrl()->GetSafeHwnd() ) );//实现表头控件的子类化
        }

有关表头控件的设计请参考实现过程部分。接下来介绍列表视图排序功能的实现。列表视图控件提供了SortItems方法用于使用自定义的方式进行排序,该方法语法如下:

BOOL SortItems(PFNLVCOMPARE pfnCompare, DWORD_PTR dwData);

参数说明:

● pfnCompare:表示用户定义的比较函数。函数原型如下:

int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);

其中,lParam1和lParam2表示待比较的两个选项,它们分别关联于两个视图项的数据值(调用SetItemData方法为视图项设置的数据值)。lParamSort表示由SortItems函数传递的dwData参数值。比较函数CompareFunc的返回值是非常关键的,如果为负数,表示第一项位于第二项的前方,为正数,表示第一项位于第二项之后,为0,表示两项相等。

● dwData:表示传递到比较函数pfnCompare的参数值(lParamSort参数)。

在进行自定义的比较时,需要在比较函数中定义比较规则。

实现过程

(1)新建一个基于对话框的应用程序。

(2)在工程中添加基于ClistCtrl的新类MyListCtrl。

(3)以CHeaderCtrl类为基类派生一个CListHeader类,以CListCtrl类为基类派生一个CListHeaderCtrl类。

(4)向对话框中添加一个列表视图控件,为控件添加CListHeaderCtrl类的成员变量m_List。

(5)向CListHeader类中添加成员变量。代码如下:

        int      m_nSortColumn;     //排序列
        BOOL     m_bAscend;         //是否为升序

(6)向CListHeader类中添加SetSortColomn方法,用于设置排序列和排序方式。在绘制表头时将根据这些信息来确定在哪个列上绘制什么样式的箭头。代码如下:

            void CFileHeaderCtrl::SetSortColomn(int nColumn,BOOL bAscend)
            {
            m_nSortColumn=nColumn;                         //记录排序列
            m_bAscend=bAscend;                             //记录排序方式,升序(TRUE)还是降序(FALSE)
            HD_ITEM hItem;
            hItem.mask = HDI_FORMAT;
            GetItem(nColumn,&hItem);                          //获取列信息
            hItem.fmt|=HDF_OWNERDRAW;                         //设置自绘风格
            SetItem(nColumn,&hItem);                          //设置列信息
            Invalidate();                                     //更新表头
        }

(7)在CFileHeaderCtrl类中改写DrawItem虚方法,根据排序列和排序方式绘制表头。代码如下:

        void CListHeader::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
        {
            CDC dc;                                  //定义设备上下文
            dc.Attach(lpDrawItemStruct->hDC);              //附加设备上下文句柄
            const int nSavedIndex=dc.SaveDC();             //保存设备上下文
            CRect rc(lpDrawItemStruct->rcItem);             //获取当前列区域
            CBrush brush(GetSysColor(COLOR_3DFACE));    //定义背景画刷
            dc.FillRect(rc,&brush);                       //填充画刷
            TCHAR szText[ 256 ];
            HD_ITEM hditem;
            hditem.mask = HDI_TEXT | HDI_FORMAT;
            hditem.pszText = szText;
            hditem.cchTextMax = 255;
            GetItem(lpDrawItemStruct->itemID,&hditem);      //获取当前的项目信息
            //设置绘制的文本格式
            UINT uFormat = DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP | DT_VCENTER | DT_END_ELLIPSIS ;
            if( hditem.fmt & HDF_CENTER)
                uFormat |= DT_CENTER;
            else if( hditem.fmt & HDF_RIGHT)
                uFormat |= DT_RIGHT;
            else
                uFormat |= DT_LEFT;
            if( lpDrawItemStruct->itemState == ODS_SELECTED )
            {
                rc.left++;
                rc.top += 2;
                rc.right++;
            }
            CRect rcIcon( lpDrawItemStruct->rcItem );
            const int iOffset = ( rcIcon.bottom - rcIcon.top ) / 4;
            if( lpDrawItemStruct->itemID == (UINT) m_nSortColumn )
                rc.right -= 3 * iOffset;
            rc.left += iOffset;
            rc.right -= iOffset;
            //绘制列文本
            if( rc.left < rc.right )
                dc.DrawText( szText, -1, rc, uFormat );
            //绘制箭头
            if( lpDrawItemStruct->itemID == (UINT) m_nSortColumn )
            {
                //定义画笔
                CPen penLight( PS_SOLID, 1, GetSysColor( COLOR_3DHILIGHT ) );
                CPen penShadow( PS_SOLID, 1, GetSysColor( COLOR_3DSHADOW ) );
                CPen*pOldPen=dc.SelectObject(&penLight); //选中画笔
                if( m_bAscend )
                {
                    //绘制向上的箭头
                    dc.MoveTo( rcIcon.right -2 * iOffset, iOffset);
                    dc.LineTo( rcIcon.right - iOffset, rcIcon.bottom - iOffset -1 );
                    dc.LineTo( rcIcon.right -3 * iOffset -2, rcIcon.bottom - iOffset -1 );
                    dc.SelectObject( &penShadow );
                    dc.MoveTo( rcIcon.right -3 * iOffset -1, rcIcon.bottom - iOffset -1 );
                    dc.LineTo( rcIcon.right -2 * iOffset, iOffset -1);
                }
                else
                {
                    //绘制向下的箭头
                    dc.MoveTo( rcIcon.right - iOffset -1, iOffset );
                    dc.LineTo( rcIcon.right -2 * iOffset -1, rcIcon.bottom - iOffset );
                    dc.SelectObject( &penShadow );
                    dc.MoveTo( rcIcon.right -2 * iOffset -2, rcIcon.bottom - iOffset );
                    dc.LineTo( rcIcon.right -3 * iOffset -1, iOffset );
                    dc.LineTo( rcIcon.right - iOffset -1, iOffset );
                }
                dc.SelectObject(pOldPen);     //恢复原来选择的画笔
            }
            dc.RestoreDC(nSavedIndex);        //恢复之前的设备上下文
            dc.Detach();                     //从设备上下文中分离设备上下文句柄
        }

(8)在CListHeaderCtrl类的头文件中声明变量,代码如下:

              CListHeader        m_ctlHeader;              //列头
            int                  m_nNumColumns;            //列数
            int                  m_nSortColumn;            //排序列
              BOOL        m_bAscend;                       //是否升序排列

(9)添加SetColumnNum方法用于设置列表列数,具体实现代码如下:

        void CListHeaderCtrl::SetColumnNum(int num)
        {
            m_nNumColumns = num;
        }

(10)向CListHeaderCtrl类中添加AddItem方法,向视图列表中添加视图项,并且为视图项关联数据(当前行所有列的文本)。代码如下:

        int CListHeaderCtrl::AddItem(LPCTSTR pszText, ... )
        {
            int nIndex=InsertItem(GetItemCount(),pszText);                   //添加行,返回行索引
            LPTSTR*pszColumnTexts=new LPTSTR[m_nNumColumns];                 //记录各列文本
            pszColumnTexts[ 0 ] = new TCHAR[ lstrlen( pszText ) + 1 ];
            lstrcpy(pszColumnTexts[0],pszText);                             //复制第一列文本到pszColumnTexts中
            va_list list;
            va_start( list, pszText );
            //设置列文本
            for(int nColumn=1;nColumn<m_nNumColumns;nColumn++)               //将各列文本复制到pszColumnTexts中
            {
                  pszText = va_arg( list, LPCTSTR );
                  CListCtrl::SetItem( nIndex, nColumn, LVIF_TEXT, pszText, 0, 0, 0, 0 );
                  pszColumnTexts[ nColumn ] = new TCHAR[ lstrlen( pszText ) + 1 ];
                  lstrcpy( pszColumnTexts[ nColumn ], pszText );
            }
            va_end(list);
            SetItemDataList(nIndex,pszColumnTexts);                         //设置视图项数据
            return nIndex;
        }

(11)向CListHeaderCtrl类中添加SetItemDataList方法,用于设置视图项关联的数据。代码如下:

        BOOL CListHeaderCtrl::SetItemDataList(int iItem, LPTSTR *pchTexts)
        {
            if (CListCtrl::GetItemData(iItem) == NULL)
            {
                  CItemData*pItemData=new CItemData();                          //创建一个CItemData对象
                  pItemData->m_ColumnTexts=pchTexts;                           //设置列文本
                  return CListCtrl::SetItemData(iItem,(DWORD)pItemData);        //设置项目数据
            }
        }

(12)处理LVN_COLUMNCLICK消息,在该消息的处理函数中调用函数设置排序的列,代码如下:

        void CListHeaderCtrl::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult)
        {
            NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
            int nColumn = pNMListView->iSubItem;
            Sort(nColumn, nColumn == m_nSortColumn ? !m_bAscend : TRUE);
            *pResult = 0;
        }

(13)添加Sort函数,用于设置排序的列,代码如下:

        void CListHeaderCtrl::Sort(int iColumn, BOOL bAscending)
        {
            m_nSortColumn = iColumn;
            m_bAscend = bAscending;
            m_ctlHeader.SetSortColomn(m_nSortColumn,m_bAscend);   //设置排序的列
            SortItems(SortFunction,(DWORD)this);                  //调用比较函数
        }

(14)定义一个用于比较的函数,其实现代码如下:

            int CALLBACK CListHeaderCtrl::SortFunction(LPARAM lParam1,LPARAM lParam2,LPARAM lParamData)
            {
            CListHeaderCtrl* pListCtrl = (CListHeaderCtrl*)(lParamData);
            CItemData*pParam1=(CItemData*)(lParam1);                            //获取视图项关联的数据
            CItemData* pParam2 = (CItemData*)(lParam2);
            LPCTSTR pszText1=pParam1->m_ColumnTexts[pListCtrl->m_nSortColumn];   //获取排序列的文本
            LPCTSTR pszText2 = pParam2->m_ColumnTexts[pListCtrl->m_nSortColumn];
            if(IsNumber(pszText1))                                              //按数值比较
                  return pListCtrl->m_bAscend ? CompareDataAsNumber(pszText1, pszText2)
                    : CompareDataAsNumber(pszText2, pszText1);
            else                                                         //按文本比较
                return pListCtrl->m_bAscend ? lstrcmp(pszText1, pszText2)
                    : lstrcmp(pszText2, pszText1);
        }

举一反三

根据本实例,读者可以:

在列表控件的列标题中显示图标。

实例064 具有文本录入功能的列带表视图控件

本实例是一个提高效率、人性化的程序

实例位置:光盘\mingrisoft\02\064

实例说明

列表视图控件简单易用,但是不能进行编辑,本实例将介绍如何使列表视图控件可编辑。运行程序,部门表的记录将显示在表格中,在表格中可以对数据进行编辑,单击“保存”按钮可以将数据保存到数据库中,如图2.25所示。

图2.25 具有文本录入功能的ListControl控件

技术要点

可以通过两种方法实现列表视图控件的可编辑功能,一种是在要编辑的单元格位置创建一个编辑框控件,另一种是创建一个编辑框控件,并将该控件移动到要编辑的单元格所在的位置。

本实例使用的是第二种方法,当用户单击表格中的单元格时,将编辑框显示在单元格中,用户可以在编辑框中输入数据,在编辑框失去焦点时将数据写入单元格。

实现过程

(1)新建一个基于对话框的应用程序。

(2)向窗体中添加一个列表视图控件。

(3)以CEdit类为基类创建新类CListEdit。

(4)处理编辑框的WM_KILLFOCUS消息,使其在失去焦点时将数据显示在列表视图控件的单元格中,代码如下:

        void CListEdit::OnKillFocus(CWnd* pNewWnd)
        {
          CGridList*temp=(CGridList*)GetParent();    //获得父窗口指针
          if(temp)
          {
          temp->DisposeEdit();                       //设置编辑框控件
          }
        }

(5)从CListCtrl类中派生一个CGridList类,改写PreSubclassWindow虚函数,为列表视图控件设置风格,创建编辑框,代码如下:

        void CGridList::PreSubclassWindow()
        {
        // 修改列表视图控件风格
        ModifyStyle(LVS_EDITLABELS,0);
        ModifyStyle(0,LVS_REPORT);
        ModifyStyle(0,LVS_SHOWSELALWAYS);
        //设置列表视图控件扩展风格
        SetExtendedStyle(LVS_EX_FLATSB
          |LVS_EX_FULLROWSELECT
            |LVS_EX_HEADERDRAGDROP
            |LVS_EX_ONECLICKACTIVATE
            |LVS_EX_GRIDLINES);
            //创建编辑框控件
            edit.Create(WS_CHILD|WS_CLIPSIBLINGS|WS_EX_TOOLWINDOW|WS_BORDER,
            CRect(0,40,10,50),this,1001);
            CListCtrl::PreSubclassWindow();
        }

(6)根据鼠标单击时的位置确定编辑框应该出现的位置,需要处理表格的WM_LBUTTONDOWN消息,以此来确定用户单击的单元格的坐标,代码如下:

        void CGridList::OnLButtonDown(UINT nFlags, CPoint point)
        {
            CListCtrl::OnLButtonDown(nFlags, point);
            LVHITTESTINFO info;
            info.pt=point;
            info.flags=LVHT_ONITEMLABEL;
            if(SubItemHitTest(&info)>=0)
            {
            row=info.iItem;
            col=info.iSubItem;
            ShowEdit();
            }
        }

(7)添加ShowEdit函数,用于显示编辑框,代码如下:

        void CGridList::ShowEdit()
        {
            CRect rect;
            GetSubItemRect(row,col,LVIR_LABEL,rect); //获得列表项区域
            CString str;
            str=GetItemText(row,col);              //获得行列信息
            edit.MoveWindow(rect);                 //移动编辑框位置
            edit.SetWindowText(str);               //设置编辑框文本
            edit.ShowWindow(SW_SHOW);              //显示编辑框
            edit.SetSel(0,100);                    //设置编辑框中文本选中
            edit.SetFocus();                       //设置编辑框焦点
            UpdateWindow();                        //更新窗口
        }

(8)添加DisposeEdit函数,为列表视图控件的单元格赋值并隐藏编辑框,代码如下:

            BOOL CGridList::DisposeEdit()
            {
            CString sLabel;
            edit.GetWindowText(sLabel);               //获得编辑框文本
            this->SetItemText(row,col,sLabel);        //插入到列表的指定单元格中
            edit.ShowWindow(SW_HIDE);                 //隐藏编辑框
            ::SendMessage(this->GetParent()->GetSafeHwnd(),NULL,NULL,NULL);
            return true;
            }

(9)为“保存”按钮添加消息响应函数,把表格中的数据添加到数据库中,代码如下:

        void CTextInListDlg::OnButsave()
        {
            ADOConn m_AdoConn;
            m_AdoConn.OnInitADOConn();              //连接数据库
            CString sql,str,str1,str2;
            sql.Format("delete from bumenbiao");      //设置删除语句
            m_AdoConn.ExecuteSQL((_bstr_t)sql);     //执行删除语句
            int m=0;
            for(int i=0;i<m_Grid.GetItemCount();i++)
            {
            //获得列表中数据
            str=m_Grid.GetItemText(i,m);
            str1=m_Grid.GetItemText(i,m+1);
            str2=m_Grid.GetItemText(i,m+2);
            if(!str.IsEmpty() || !str1.IsEmpty() || !str2.IsEmpty())
            {
              //将列表中数据插入到数据库中
              sql.Format("insert into bumenbiao (职业编号,职业名称,负责人) values ('%s','%s','%s')",str,str1,str2);
              m_AdoConn.ExecuteSQL((_bstr_t)sql);   //执行插入语句
            }
            }
            m_AdoConn.ExitConnect();
            MessageBox("保存完毕");
        }

举一反三

根据本实例,读者可以:

实现联想录入功能。

实例065 使用列表视图设计登录界面

这是一个可以启发思维的实例

实例位置:光盘\mingrisoft\02\065

实例说明

用户在登录一些软件时,经常可以看到用户名是以图标的形式显示在列表中的。本实例就是使用列表视图设计登录界面,实例运行结果如图2.26所示。

图2.26 使用列表视图设计登录界面

技术要点

首先创建一个图像列表,并通过SetImageList方法将列表视图控件和图像列表关联到一起。语法如下:

CImageList* SetImageList( CImageList* pImageList, int nImageList );

参数说明:

● pImageList:标识图像列表指针。

● nImageList:标识图像列表类型,可选值如下。

■ LVSIL_NORMAL:图像列表具有大图标。

■ LVSIL_SMALL:图像列表具有小图标。

■ LVSIL_STATE:图像列表具有状态图像。

然后调用InsertItem方法向列表视图控件插入数据。语法如下:

        int InsertItem( int nItem, LPCTSTR lpszItem );
        int InsertItem( int nItem, LPCTSTR lpszItem, int nImage );
        int InsertItem( UINT nMask, int nItem, LPCTSTR lpszItem, UINT nState, UINT nStateMask, int nImage, LPARAM lParam );

参数说明:

● pItem:是LVITEM结构指针,LVITEM结构中包含的视图项的文本、图像索引、状态等信息。

● nItem:表示被插入的视图项索引。

● lpszItem:表示视图项文本。

● nImage :表示视图项图像索引。

● nMask:一组标记,用于确定哪一项信息是合法的。

● nState:表示视图项的状态。

● nStateMask:确定设置视图项的哪些状态。

● lParam:表示关联视图项的附加信息。

实现过程

(1)新建一个基于对话框的应用程序。

(2)向工程中导入7个图标资源。

(3)向对话框中添加一个列表视图控件、一个静态文本控件、一个编辑框控件和一个按钮控件。

(4)在对话框头文件中声明一个图像列表对象m_ImageList。

(5)主要程序代码如下:

        BOOL CLoginDlg::OnInitDialog()
        {
            CDialog::OnInitDialog();
            // …系统代码省略
            m_ImageList.Create(32,32,ILC_COLOR24|ILC_MASK,1,0);        //创建列表视图窗口
            m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON1));         //向图像列表中添加图标
            m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON2));         //向图像列表中添加图标
            m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON3));         //向图像列表中添加图标
            m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON4));         //向图像列表中添加图标
            m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON5));         //向图像列表中添加图标
            m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON6));         //向图像列表中添加图标
            m_ImageList.Add(AfxGetApp()->LoadIcon(IDI_ICON7));         //向图像列表中添加图标
            m_Icon.SetImageList(&m_ImageList,LVSIL_NORMAL);            //将图像列表关联到列表视图控件中
            m_Icon.InsertItem(0,"王一",0);                             //向列表视图中添加数据
            m_Icon.InsertItem(1,"孙二",1);                             //向列表视图中添加数据
            m_Icon.InsertItem(2,"刘三",2);                             //向列表视图中添加数据
            m_Icon.InsertItem(3,"吕四",3);                             //向列表视图中添加数据
            m_Icon.InsertItem(4,"庞五",4);                             //向列表视图中添加数据
            m_Icon.InsertItem(5,"宋六",5);                             //向列表视图中添加数据
            m_Icon.InsertItem(6,"孙七",6);                             //向列表视图中添加数据
            return TRUE;
        }

举一反三

根据本实例,读者可以:

设计腾讯OICQ抽屉界面。