TListView 的 Header 部分默认 BtnFace 颜色,高度也不能改变。我们可以通过编写一些代码来实现这些功能;

 

  • 获得TListView 的Header 的句柄;

    TListView的Header其实是一个 HeaderContorl 控件。要获得他的句柄需要调用下面的代码

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------

static HWND hListViewHeader = NULL;

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::FormCreate(TObject *Sender)
{
//获得ListView Header的句柄
    hListViewHeader = ListView_GetHeader(ListView1->Handle);
}
//---------------------------------------------------------------------------

 

其实还有许多ListView_XXXX 这样的windows API ,需要查看MSDN;

 

  • 改变Header的高度

Header的高度,是在一个处理一个叫 HDM_LAYOUT 的消息的时候进行设置。

msdn的原话如下:

 

那么我们就用SetWindowLong 来改变 Header的 消息过程。

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------


static HWND hListViewHeader = NULL;

//Header原来的窗口过程
static WNDPROC oldHeaderWindowProc = NULL;

//Header的新窗口过程
LRESULT CALLBACK NewHeaderWindowProc(
      HWND hwnd,
      UINT uMsg,
      WPARAM wParam,
      LPARAM lParam)
{
//先要用原来的窗口过程处理
    LRESULT result = oldHeaderWindowProc(hwnd,uMsg,wParam,lParam);
//再处理HDM_LAYOUT消息
    if (HDM_LAYOUT == uMsg)
    {
        LPHDLAYOUT phdmlayout = (LPHDLAYOUT)lParam;
        //改变header的高度
        phdmlayout->pwpos->cy = 27;
        //改变listview表格部分的高度
        phdmlayout->prc->top  = phdmlayout->pwpos->cy;
    }

return result; } __fastcall TForm1::TForm1(TComponent
* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { hListViewHeader = ListView_GetHeader(ListView1->Handle); oldHeaderWindowProc = (WNDPROC)SetWindowLong( hListViewHeader,GWL_WNDPROC,(LONG)NewHeaderWindowProc); ListView1->Invalidate(); } //---------------------------------------------------------------------------

 

我们会看到这样的效果

 

 

  • 处理Header的背景色

可以在Header的窗口过程中编写WM_PAINT 消息处理函数。但是这样写,你会发现无法在Header上输出文字。准确的说,你绘制上去的文字

会莫名其妙的不见,比如,拉宽一下Header 中的某一列,文字就会消失边检。使得这种重绘不好控制;

 

正确的做法是这样

  1. 调用 Header_SetItem 函数(其实是一个macro) 将ListView的Header 的 format 属性,设置为 HDF_OWNERDRAW (默认是 HDF_STRING).
  2. 重写ListView 的消息过程。(不是Header的消息过程);
  3. 在ListView的消息过程中处理 WM_DRAWITEM 消息;(WM_DRAWITEM消息会包含要重绘的 区域,hdc,index,状态等等)
  4. 在Header 的 HDM_LAYOUT 消息处理过程,也要调用 Header_SetItem,将format消息设置为 HDF_OWERDRAW (否则,每次改变列宽,format属性就又变成HDF_STRING,导致无法重绘)

下面是实现的代码

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------


static HWND hListViewHeader = NULL;

//Header原来的窗口过程
static WNDPROC oldHeaderWindowProc = NULL;

//listView 原来的窗口过程
static WNDPROC oldListViewProc = NULL;

//Header的新窗口过程
LRESULT CALLBACK NewHeaderWindowProc(
      HWND hwnd,
      UINT uMsg,
      WPARAM wParam,
      LPARAM lParam)
{
//先要用原来的窗口过程处理
    LRESULT result = oldHeaderWindowProc(hwnd,uMsg,wParam,lParam);
//再处理HDM_LAYOUT消息
    if (HDM_LAYOUT == uMsg)
    {
        LPHDLAYOUT phdmlayout = (LPHDLAYOUT)lParam;
        //改变header的高度
        phdmlayout->pwpos->cy = 27;
        //改变listview表格部分的高度
        phdmlayout->prc->top  = phdmlayout->pwpos->cy;
    }

    return result;
}


LRESULT CALLBACK NewListViewWindowProc(
      HWND hwnd,
      UINT uMsg,
      WPARAM wParam,
      LPARAM lParam)
{
    if (uMsg == WM_DRAWITEM)
    {
        DRAWITEMSTRUCT* drawItemStruct = (DRAWITEMSTRUCT*)lParam;
        HDC hdc = drawItemStruct->hDC;
        int index = drawItemStruct->itemID;
        RECT rect = drawItemStruct->rcItem;

        TBrush* brush = new TBrush();
        TColor fontColor = clBlack;
        if (index == 0)
        {
            brush->Color = clRed;
            fontColor    = clWhite;
        }

        if (index == 1)
        {
            brush->Color = clYellow;
            fontColor    = clBlue;
        }

        if (index == 2)
        {
            brush->Color = clBlue;
            fontColor    = clWhite;
        }

        if (index == 3)
        {
            brush->Color = clLime;
            fontColor    = clBlue;
        }

        FillRect(hdc,&rect,brush->Handle);
        delete brush;

        SetTextColor(hdc,fontColor);
        SetBkMode(hdc,TRANSPARENT);
        //获取文本
        HDITEM hditem= {0};
        char buf[100] = {0};
        hditem.mask = HDI_TEXT;
        hditem.pszText = buf;
        hditem.cchTextMax = 100;
        Header_GetItem(hListViewHeader,index,&hditem);

        DrawText(hdc,hditem.pszText,strlen(hditem.pszText),&rect,
        DT_CENTER |DT_VCENTER |DT_SINGLELINE);


    }

    return oldListViewProc(hwnd,uMsg,wParam,lParam);
}

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void SetHDItem()
{
    if (hListViewHeader == NULL)
    {
        return;
    }

    HDITEM hditem;
    hditem.mask = HDI_FORMAT;
    hditem.fmt  = HDF_OWNERDRAW; //默认是 HDF_STRING

    int count = Header_GetItemCount(hListViewHeader);
    for (int i = 0; i < count ;i++)
    {
        Header_SetItem(hListViewHeader,i,&hditem);
    }
}

void __fastcall TForm1::FormCreate(TObject *Sender)
{
    hListViewHeader = ListView_GetHeader(ListView1->Handle);
    
    oldHeaderWindowProc = (WNDPROC)SetWindowLong(
        hListViewHeader,GWL_WNDPROC,(LONG)NewHeaderWindowProc);

    oldListViewProc = (WNDPROC)SetWindowLong(
        ListView1->Handle,GWL_WNDPROC,(LONG)NewListViewWindowProc);

    SetHDItem();

    ListView1->Invalidate();
}
//---------------------------------------------------------------------------

 

显示效果如下

但是,一旦拖动列头会变成这样

所以需要有上述步骤中第4步,在HDM_LAYOUT消息中加入SetHDItem 函数调用

最终代码

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------


static HWND hListViewHeader = NULL;

//Header原来的窗口过程
static WNDPROC oldHeaderWindowProc = NULL;

//listView 原来的窗口过程
static WNDPROC oldListViewProc = NULL;


void SetHDItem()
{
    if (hListViewHeader == NULL)
    {
        return;
    }

    HDITEM hditem;
    hditem.mask = HDI_FORMAT;
    hditem.fmt  = HDF_OWNERDRAW; //默认是 HDF_STRING

    int count = Header_GetItemCount(hListViewHeader);
    for (int i = 0; i < count ;i++)
    {
        Header_SetItem(hListViewHeader,i,&hditem);
    }
}

//Header的新窗口过程
LRESULT CALLBACK NewHeaderWindowProc(
      HWND hwnd,
      UINT uMsg,
      WPARAM wParam,
      LPARAM lParam)
{
//先要用原来的窗口过程处理
    LRESULT result = oldHeaderWindowProc(hwnd,uMsg,wParam,lParam);
//再处理HDM_LAYOUT消息
    if (HDM_LAYOUT == uMsg)
    {
        LPHDLAYOUT phdmlayout = (LPHDLAYOUT)lParam;
        //改变header的高度
        phdmlayout->pwpos->cy = 27;
        //改变listview表格部分的高度
        phdmlayout->prc->top  = phdmlayout->pwpos->cy;
        //将HDF_STRING 重新设置为 HDF_OWNERDRAW
        SetHDItem();
    }

    return result;
}


LRESULT CALLBACK NewListViewWindowProc(
      HWND hwnd,
      UINT uMsg,
      WPARAM wParam,
      LPARAM lParam)
{
    if (uMsg == WM_DRAWITEM)
    {
        DRAWITEMSTRUCT* drawItemStruct = (DRAWITEMSTRUCT*)lParam;
        HDC hdc = drawItemStruct->hDC;
        int index = drawItemStruct->itemID;
        RECT rect = drawItemStruct->rcItem;

        TBrush* brush = new TBrush();
        TColor fontColor = clBlack;
        if (index == 0)
        {
            brush->Color = clRed;
            fontColor    = clWhite;
        }

        if (index == 1)
        {
            brush->Color = clYellow;
            fontColor    = clBlue;
        }

        if (index == 2)
        {
            brush->Color = clBlue;
            fontColor    = clWhite;
        }

        if (index == 3)
        {
            brush->Color = clLime;
            fontColor    = clBlue;
        }

        FillRect(hdc,&rect,brush->Handle);
        delete brush;

        SetTextColor(hdc,fontColor);
        SetBkMode(hdc,TRANSPARENT);
        //获取文本
        HDITEM hditem= {0};
        char buf[100] = {0};
        hditem.mask = HDI_TEXT;
        hditem.pszText = buf;
        hditem.cchTextMax = 100;
        Header_GetItem(hListViewHeader,index,&hditem);

        DrawText(hdc,hditem.pszText,strlen(hditem.pszText),&rect,
        DT_CENTER |DT_VCENTER |DT_SINGLELINE);


    }

    return oldListViewProc(hwnd,uMsg,wParam,lParam);
}

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------


void __fastcall TForm1::FormCreate(TObject *Sender)
{
    hListViewHeader = ListView_GetHeader(ListView1->Handle);
    
    oldHeaderWindowProc = (WNDPROC)SetWindowLong(
        hListViewHeader,GWL_WNDPROC,(LONG)NewHeaderWindowProc);

    oldListViewProc = (WNDPROC)SetWindowLong(
        ListView1->Handle,GWL_WNDPROC,(LONG)NewListViewWindowProc);

    SetHDItem();

    ListView1->Invalidate();
}
//---------------------------------------------------------------------------

 

---2016年6月15日更新

 

上个版本虽然实现了效果,但是有一个bug,双击列头中间的分割区域,会导致背景色丢失;

 

解决办法是,

  1. 在ListView 的消息过程中,处理 WM_NOTIFY 消息
  2. 在WM_NOTIFY中处理Header发来的HDN_DIVIDERDBLCLICK 消息
  3. WM_NOTIFY消息,一定要在默认消息处理完之后执行
LRESULT CALLBACK NewListViewWindowProc(
      HWND hwnd,
      UINT uMsg,
      WPARAM wParam,
      LPARAM lParam)
{
    if (uMsg == WM_DRAWITEM)
    {
        DRAWITEMSTRUCT* drawItemStruct = (DRAWITEMSTRUCT*)lParam;
        HDC hdc = drawItemStruct->hDC;
        int index = drawItemStruct->itemID;
        RECT rect = drawItemStruct->rcItem;

        TBrush* brush = new TBrush();
        TColor fontColor = clBlack;
        if (index == 0)
        {
            brush->Color = clRed;
            fontColor    = clWhite;
        }

        if (index == 1)
        {
            brush->Color = clYellow;
            fontColor    = clBlue;
        }

        if (index == 2)
        {
            brush->Color = clBlue;
            fontColor    = clWhite;
        }

        if (index == 3)
        {
            brush->Color = clLime;
            fontColor    = clBlue;
        }

        FillRect(hdc,&rect,brush->Handle);
        delete brush;

        SetTextColor(hdc,fontColor);
        SetBkMode(hdc,TRANSPARENT);
        //获取文本
        HDITEM hditem= {0};
        char buf[100] = {0};
        hditem.mask = HDI_TEXT;
        hditem.pszText = buf;
        hditem.cchTextMax = 100;
        Header_GetItem(hListViewHeader,index,&hditem);

        DrawText(hdc,hditem.pszText,strlen(hditem.pszText),&rect,
        DT_CENTER |DT_VCENTER |DT_SINGLELINE);


    }

    LRESULT result = oldListViewProc(hwnd,uMsg,wParam,lParam);

    //--双击列的分隔区事件
    if (uMsg == WM_NOTIFY)
    {
         NMHDR* nmhdr = (NMHDR*)lParam;
         if (nmhdr != NULL &&
             nmhdr->hwndFrom == hListViewHeader &&
             nmhdr->code == HDN_DIVIDERDBLCLICK)
         {
            //将HDF_STRING 重新设置为 HDF_OWNERDRAW
            SetHDItem();
         }
    }

    return result;
}

 之后双击分隔区的效果

 

posted on 2016-06-09 17:32  zooz  阅读(2212)  评论(0编辑  收藏  举报