Listview 的自绘

开发的过程中,经常要使用到 TListview,为了界面美观,需要自绘 TListview,下面就 TListview 自绘的三个方面,总结一下。
  1、显示千万条数据记录;   
  2、表格自绘;              
  3、表头自绘;  
         

  1、显示千万条数据记录;
 
     A:OwnerData 设为 True, OwnerDraw 暂时设为 False, 不进行自绘,由系统来完成绘制。
            我们的重点放在显示千万条记录上面;
     
     B:数据来源,数据库查询:

 ADOQuery1.SQL.Clear; 
 ADOQuery1.Close;
 ADOQuery1.SQL.Text := 'select ROW_NUMBER() over(order by ID) as RowNum, * from TableName';
 ADOQuery1.Open;

        这里:
        ID                :数据库表中自增长字段(换成你要查询的数据库表中的自增长字段);
        TableName :数据库表名;
        *                  :数据库中所有字段名称;
                               实际使用中,最好不要用 * ,需要显示哪些字段就填入字段名称 ,效率会大大提高。
         注:                        
          ID 是必须的,为最后一列,不显示的一列,用于记录标识;当选中/双击时,根据此列值,可找到数据库对应记录。

          无论你的查询语句如何写,select ROW_NUMBER() over(order by ID) as RowNum 是必不可少的
      
      ADOQuery1 查询结束后,添加代码:

lvData.Items.Count := ADOQuery1.RecordCount; 

     将触发 OnData 事件:

     C:双击 OnData 事件,进入代码界面:
        假设 ADOQuery1 查询结果中包含千万条记录
        显示数据前,列名先准备好。动态创建也可以。
        注:
         列名一般使用字段名,但字段名一般为英文。实际使用肯定是需要中文的。
         我们可以使用字段的说明属性,来当作列名。
         建立字段时,顺手给字段的列属性中的说明属性加个中文说明,
         程序中获取字段对应的说明属性来当作列名,就OK了。

      procedure TfrmListViewData.lvDataData(Sender: TObject; Item: TListItem);
      var
        I: Integer;
      begin
        ADOQuery1.RecNo := Item.Index + 1;
        Item.Caption    := ADOQuery1.Fields[1].AsString;
        for I           := 2 to ADOQuery1.Fields.Count - 1 do
        begin
          if ADOQuery1.Fields[I].DataType = ftBlob then
            Item.SubItems.Add('')
          else
            Item.SubItems.Add(ADOQuery1.Fields[I].AsString);
        end;
      end;

        OK,大功告成。上面代码只是示例,细节根据自己需要,自己把控。
      
  2、表格自绘; 
       
这一步比较容易;
        A:设置 OwnerDraw 为 True ,就会触发自绘事件;
        B:双击 OnCustomDrawItem 事件,在事件中输入自绘代码。
              有画布 Canvas,TListview(Sender).Canvas,有 Item ,想绘制成啥样,就绘制成啥样。
              划过/选中/动态,等等效果都可以在这里实现。
    
  3、表头自绘;        
      想绘制表头,就需要知道表头句柄。
      想得到表头句柄,就需要拦截 ListView 的 WMParentNotify 消息,因为表头的创建是由它完成的。     
      类定义:

type
        TListView = Class(ComCtrls.TListView)
        private
            FHeaderHandle: Integer;
            { 表头的回调过程 }
            FOldHeaderProc, FHeaderInstance: Pointer;
        protected
          procedure WMParentNotify(var Message: TWMParentNotify); message WM_PARENTNOTIFY;
          procedure HeaderWndProc(var Message: TMessage);
      end;

        实现:

procedure TListView.WMParentNotify(var Message: TWMParentNotify);
begin
  if (Message.Event = WM_CREATE) then
  begin
    FHeaderHandle  := message.ChildWnd;                                       // 获取到表头句柄
    FOldHeaderProc := Pointer(GetWindowLong(FHeaderHandle, GWL_WNDPROC));     // 保存好表头原有的回调过程
    SetWindowLong(FHeaderHandle, GWL_WNDPROC, Integer(FHeaderInstance));      // 指向表头新的回调过程,用于自绘
  end;
  inherited;
end;

     注:
         这里还有一些细节,比如:
         WINXP、WIN7、WIN10下创建表头句柄是有点差别的;
         创建时,需要经过几次才会真正得到句柄;
        上面的代码只是核心代码,细化没有写出,自己动手试试,也就能理解了。

     

     事前要设置一下表头新的回调过程: 

FHeaderInstance := MakeObjectInstance(HeaderWndProc);
 { 自绘表头 }
 procedure TListView.HeaderWndProc(var Message: TMessage);
 begin
   with Message do
   begin
   { 自绘表头 }
     if msg = WM_PAINT then
     begin
       { 有句柄 FHeaderHandle,就可以得到画布 Canvas ,有画布就可以自绘了 }
     end;
   end;
 end;

注:
  还有其它消息可以处理:
  WM_SETCURSOR  :当鼠标移动到表头分割线时;
  WM_NCHITTEST   :调整表头宽度;
  HDM_LAYOUT       :调整表头高度;
  等等,绘制表头悉数掌控。


OK,绘制完毕。

效果图:
效果图
 

滚动条的绘制比较好玩。以后在写吧。不在 TListview 的自绘范围内。
它是一个通用的绘制类,可以给任意控件来用(除了IE控件)。
滚动条自绘

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章