TListViewのvsReport形式でチェックボックスをチェックなし、あり、不可(グレー)にしたい


ぱっく  2010-03-26 03:50:01  No: 38112

はじめまして。
Delphi&Windowsプログラミング歴10日目で右も左も分かりませんが、よろしくお願いします。

TListViewのvsReport形式を使い、CheckBoxesをTrueにしています。
チェックボックスを「チェックなし」、「チェックあり」、「チェック不可(グレー色)」に
したいと考えました。
サンプルを探しても見つからなかったので、難しい方法しかないのかとあきらめかけてましたが、
ImageListをStateImagesに設定し、StateIndexを操作すれば可能なことを知り、やってみました。
表示された自作画像のチェックボックスをクリックすると、チェックなし→ありと変化して、
成功したように見えたんですが…大問題が。クリックしても、チェックの状態変化がおかしいのです。
正常なら Item.Checked=False、True、False、True、…と変化するはずですが、
なぜか False、True、True、True、False、True、True、True、False、True、…となります。
表示だけの問題か、チェックに制限を設けている部分の問題かと思いましたが、分かりませんでした。
そもそも無理な使い方をしているのでしょうか。でも一応クリックに反応はするんですよね…。
以下がソースの抜粋です。実際には他の処理も入ってます。

Const
  ResultSII=3;    // ステータスを格納するSubItemのIndex
  M_kanou='可能'; // ステータスの各表示内容
  M_huka= '不可'; //

procedure TForm1.ListView1CustomDrawItem(Sender: TCustomListView;
  Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
begin
  // SubItemの数がResultSIIまで達していないItemはグレーに
  if Item.SubItems.Count<ResultSII+1 then
    begin
      Item.StateIndex:=2;
      exit;
    end;
  // SubItems[ResultSII]が「可能」のItemはStateIndexをCheckedに合わせ、それ以外はグレーに
  if Item.SubItems[ResultSII]=M_kanou then
    if Item.Checked then Item.StateIndex:=1
    else Item.StateIndex:=0
  else
    Item.StateIndex:=2;
end;

procedure TForm1.ListView1Change(Sender: TObject; Item: TListItem;
  Change: TItemChange);
begin
  // SubItemの数がResultSIIまで達していないItemは無条件でChecked:=False
  if Item.SubItems.Count<ResultSII+1 then Item.Checked:=False
  else
  //SubItems[ResultSII]が「可能」以外のItemは、Checked=Trueにさせない
    if Item.SubItems[ResultSII]<>M_kanou then Item.Checked:=False;
end;

TListViewのvsReport形式でチェックボックスの状態を
・ 「チェックなし」、「チェックあり」、「チェック不可(グレー色)」 または、
・ 「チェックなし」、「チェックあり」、「チェック不可(グレー色)」、「チェック強制(グレー色)」
のようにしたいという需要は結構あると思うのですが、検索した限りでは見つかりませんでした。
どのようにすれば良いのか、教えていただければ幸いです。


そもそも  2010-03-26 19:08:36  No: 38113

提示されたソースではListViewが酷くちらついて使い物にならないけど。
SubItems[ResultSII]の「可能」と「不可」の切り替えはどうやってるの?


ぱっく  2010-03-27 03:40:25  No: 38114

>そもそもさん
いえ、ひどくちらつくということは無いです。CPUが1.2GHzの古PCだからでしょうか。
それとも、まだ項目(=Itemでいいんですよね?)を仮に8個程度しか入れてないからかな?
なお、仮の項目はIDEで手入力しました。項目数は最大で50個にするつもりです。
ステップ実行等してみると何回か描画してるようで、クリックされた項目だけは2〜3回描画を
してました。
目を凝らすと確かに、クリックされた項目だけはその瞬間ちらついているように感じます。
正しくはどういう作りにするべきなのでしょうか?

「可能」と「不可」の切り替えですが、そういう部分はまだ作っていません。
「可能」は、あるメインの処理が終わったら「完了」に切り替えるつもりです。
「不可」や「完了」は変化しません。


tor  2010-03-27 05:33:14  No: 38115

ListViewのチェックボックスの状態は、StateIndexを使って管理されています。
(MSDNによると1がUncheckedで2がCheckedだとか)
さらに、VCLもCheckedとStateIndex値の値を別々にキャッシュしているので、話がややこしくなっています。
結局、それぞれが干渉し合って正しく状態が更新されないようですね。
いっそCheckBoxesは外して、StateIndexだけ使って自前で管理するのが良いのではないでしょうか。

ついでに今後問題になりそうなところ:
・OnCustomDrawItemは、状態が変わった「結果」として再描画のため呼び出されるものなので、そこで状態を変えるべきではありません。
・Changedはリスト項目の状態が変わるとその都度呼び出されます(例えば、リストの選択を変更するだけでも数回続けて呼ばれます)。
なので、SubItemとかCheckedが変更された時だけ呼び出されると仮定するのは危険。
また、その内部でCheckedなどの状態を変更すると、無限呼び出しに陥る可能性があります。


monaa  2010-03-27 07:03:32  No: 38116

初めて使ってみましたが、確かに挙動不審ですね。
こんな感じでどうでしょう?

procedure TForm1.FormCreate(Sender: TObject);
var
  i,p: Integer;
  str:string;
  item:TListItem;
begin
  ListView1.Checkboxes:=True;
  for i := 0 to 10 - 1 do
  begin
    item := ListView1.Items.Add;
    p := i mod 4;
    case p of
      0:str:='未チェック';
      1:str:='チェック';
      2:str:='絶対未チェック';
      3:str:='絶対チェック';
    end;
    item.Caption := str + IntToStr(i);
    Item.StateIndex := p;
  end;
end;

procedure TForm1.ListView1AdvancedCustomDrawItem(Sender: TCustomListView;
  Item: TListItem; State: TCustomDrawState; Stage: TCustomDrawStage;
  var DefaultDraw: Boolean);
begin
  if item.StateIndex >= 2 then
    ListView1.Canvas.Font.Color := clRed;
end;

procedure TForm1.ListView1ItemChecked(Sender: TObject; Item: TListItem);
begin
  if item=nil then
    Exit;
  if item.StateIndex >= 2 then
  begin
    item.Checked := not Item.Checked;
    item.Checked := (item.StateIndex mod 2 = 1);
  end;
end;


ぱっく  2010-03-27 20:06:00  No: 38117

>torさん、アドバイスありがとうございます!
StateIndexで管理してるとは思いもよりませんでした。生のListViewコントロールでは、
という話ですよね。
でもそれで不都合が発生するのって、もしかしてVCLにバグがあるんじゃ…。
自前で管理となると、OnClick、OnMouseDownとか使うのですよね。何を使えば良いのでしょう?
こちらの手法などが応用できるのでしょうか。
https://www.petitmonte.com/bbs/answers?question_id=5651

今後問題になりそうなところ...ですが、様々な場合に呼び出されるであろうことは想定してました。
が、Checkedの変化によって呼び出されるイベントの内部でCheckedの状態を変えるのは危険なのですね。
例えばですが、「特定のチェックボックスの状態は変化させない」という条件で、上記のコードの
OnChange部分をOnChangingに移し、AllowChangeパラメータをFalseにするのはセーフなのでしょうか。

なお、余計な内容を除去した下記のコードで実験を繰り返しました。
ListView、Memo、Timer、チェックボックス画像を入れたImageListを配置してます。
その成果は…間違い無くCheckedの状態変化がおかしいことが判明しただけでした(涙)

var
  ItemChkd: Boolean;
  StateIdx:Integer;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Timer1.Enabled:=False;                    //タイマーを止めておく
  Memo1.ScrollBars:=ssBoth;

  ListView1.ViewStyle:=vsReport;            //ListViewの初期化
  ListView1.Columns.Add;
  ListView1.Checkboxes:=True;
  ListView1.ReadOnly:=True;
  ListView1.StateImages:=ImageList1;
  ListView1.Items.Add.Caption:='テスト';    //テスト用に1個だけItemを置く
  ListView1.Items[0].StateIndex:=0;         //0=チェックON画像、1=チェックOFF画像

  ItemChkd:=not ListView1.Items[0].Checked; //Checkedの変化検出用
  StateIdx:=19;                             //StateIndexの変化検出用

  Timer1.Interval:=1;                       //タイマー始動
  Timer1.Enabled:=True;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  LIC:Boolean;
  LIS:Integer;
begin
  LIC:=ListView1.Items[0].Checked;
  LIS:=ListView1.Items[0].StateIndex;
  if (ItemChkd<>LIC) or (StateIdx<>LIS) then
    begin
      Memo1.Lines.Add(IfThen(LIC,'True  ','False ')+IntToStr(LIS));
      ItemChkd:=LIC;
      StateIdx:=LIS;
    end;
end;

procedure TForm1.ListView1Changing(Sender: TObject; Item: TListItem;
  Change: TItemChange; var AllowChange: Boolean);
var
  StrC:String;
begin
  case Change of
    ctText:  StrC:='ctText';
    ctImage: StrC:='ctImage';
    ctState: StrC:='ctState';
  end;
  Memo1.Lines.Add('Changing!!  '+StrC);
end;

end.


それなら  2010-03-27 20:09:07  No: 38118

>ImageListをStateImagesに設定し、StateIndexを操作すれば可能なことを知り、やってみました。
>表示された自作画像のチェックボックスをクリックすると、チェックなし→ありと変化...

StateImagesに指定したImageListの中の自作画像が以下のようになっているとして、

 0番目:チェックなし
 1番目:チェックあり
 2番目:グレー(チェックなし)
 3番目:グレー(チェックあり)

自前の画像でチェックボックスを表示する場合、Checkboxesプロパティは Falseにして、以下のコードでチェックを切り換えればいい。
なお、Checkedプロパティはチェックの有無の判定には使えないので、StateIndexの値で判定する。

type
  TListView = class(ComCtrls.TListView)
  private
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  end;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    ......
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

const
  ResultSII = 3;
  M_Kanou = '可能'; // チェック ON/OFF変更可
  M_Fuka = '不可';  // グレー (ON/OFFはそのまま)

// チェックのON/OFF
procedure TListView.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  Item: TListItem;
begin
  inherited;
  Item := GetItemAt(X, Y);
  if Item = nil then exit;
  if Item.SubItems[ResultSII] = M_Kanou then begin
    if htOnStateIcon in GetHitTestInfoAt(X, Y) then begin
      if Item.StateIndex = -1 then Item.StateIndex := 0;
      Item.StateIndex := 1 - Item.StateIndex;
    end;
  end;
end;

※ 各ItemのStateIndexは、項目設定時にあらかじめセットしておくこと。
※「可能」から「不可」にするには、Item.StateIndex := Item.StateIndex or 2;
※「不可」から「可能」にするには、Item.StateIndex := Item.StateIndex and 1;


ぱっく  2010-03-27 20:12:34  No: 38119

上記のリスト中、私にとって肝心な所のコメント部分を間違えました。正しくは、
  ListView1.Items[0].StateIndex:=0;         //0=チェックOFF画像、1=チェックON画像
です。


ぱっく  2010-03-27 20:28:09  No: 38120

あららっ! ちょっと書き込みがかぶってました。
そもそもさんですね!!
サンプルコードをありがとうございます!!!
しかし、現時点での私の理解を超える内容のような気がしますので、しばらく研究させていただいて
からコメントいたします。


ぱっく  2010-03-27 20:31:45  No: 38121

>monaaさん、サンプルコードまで書いていただいて、ありがとうございます!
挙動不審なことを理解していただいてうれしいです。一人で悩んでいたものですから。
ObjectPascalプログラムを構成する要素がまるで理解できていないので、一行一行が参考になります。

ただ…これはStateIndexの内容によりCheckedの状態変化に制限を加え、さらに項目の色も変える、
というサンプルですよね??
すみません、確かにプログラム中のどこでCheckedの状態変化を制限するのかという課題もあるのですが、
3状態のチェックボックスをどうやってうまく表示させるか→どうやってチェックボックスがクリック
されたことを検出するのか、というのが当面の課題になってきております…。

あの、その、、、後出し環境で大変申し訳無いのですが、Delphi 6 Personalを使用しておりまして、
OnItemCheckedというイベントが見当たらないのです…。
調べた限りでは、Delphi 2009からの新機能ではないかと(ググっても情報が少ないです)。
☆Delphi 2009 および C++Builder 2009 の新機能 - RAD Studio(共通)
http://pdf.openvista.jp/view/medium/p76-77/http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate1/JA/pdf/devcommon.pdf
※2ページ目にOnItemCheckedについて載ってます

せっかく教えていただいたのに活用できなくてすみません。
でもOnItemCheckedイベント、気になります。チェックボックスのクリックで呼び出され、その中では
Checkedの変更をしてOkですよ、というものでしょうか?

あと、Checkedの状態変化をStateIndexが0,1なら制限無し、2なら常時OFF、3なら常時ONとするならば、
    item.Checked := not Item.Checked;            ←下の行で決まってしまうような??
    item.Checked := (item.StateIndex mod 2 = 1); ←(item.StateIndex = 3); ではダメですか?
もしくは、
  item.Checked := not Item.Checked;
  if item.StateIndex >= 2 then
    item.Checked := (item.StateIndex mod 2 = 1);
なら、つじつまが合うような気がするのですが…。
monaaさんの意図している動作と、私の求めている動作が違っていたらすみません。

さらにちょっと疑問なんですが、OnCustomDrawでなくOnAdvancedCustomDrawにはどういった利点がある
のでしょうか。
OnCustomDrawのヘルプによると、「…デフォルトの描画処理を他のステージ(背景が消去されたとき,
またはリストビューの描画が完了したときなど)でも実行するには,OnAdvancedCustomDraw イベントを
かわりに使います。」となっていますが、ちんぷんかんぷんです…。


monaa  2010-03-27 23:24:26  No: 38122

手元にD7以下が無いのでD7で動作確認しました。
これで動作しますかね?
ListViewはOSのバージョンの制約を受けます、
ご自分でチェックイメージを作成するなら、もう少し簡単かもしれません。
今時間が無いのでコメントは読んでません、また後で来ます。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, CommCtrl;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    procedure FormCreate(Sender: TObject);
    procedure ListView1AdvancedCustomDrawItem(Sender: TCustomListView;
      Item: TListItem; State: TCustomDrawState; Stage: TCustomDrawStage;
      var DefaultDraw: Boolean);
  private
    { Private 宣言 }
    procedure ListViewWindowProcEx(var Message: TMessage) ;
    procedure ListView1ItemChecked(Item: TListItem);
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;
  OriginalListViewWindowProc:TWndMethod;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  i,p: Integer;
  str:string;
  item:TListItem;
begin
  OriginalListViewWindowProc := ListView1.WindowProc;
  ListView1.WindowProc := ListViewWindowProcEx;

  ListView1.Checkboxes:=True;
  for i := 0 to 10 - 1 do
  begin
    item := ListView1.Items.Add;
    p := i mod 4;
    case p of
      0:str:='未チェック';
      1:str:='チェック';
      2:str:='絶対未チェック';
      3:str:='絶対チェック';
    end;
    item.Caption := str + IntToStr(i);
    Item.StateIndex := p;
    item.Checked := not Item.Checked; //ここがミソ
    item.Checked := not Item.Checked; //ここがミソ
  end;
end;

procedure TForm1.ListView1ItemChecked(Item: TListItem);
begin
  if item=nil then
    Exit;
  if item.StateIndex >= 2 then
    item.Checked := (item.StateIndex mod 2 = 1);
end;

//This code was copyed from...
//http://delphi.about.com/od/delphitips2007/qt/listviewchecked.htm
procedure TForm1.ListViewWindowProcEx(var Message: TMessage) ;
var
  listItem : TListItem;
begin
  if Message.Msg = CN_NOTIFY then
  begin
    if PNMHdr(Message.LParam)^.Code = LVN_ITEMCHANGED then
    begin
      with PNMListView(Message.LParam)^ do
      begin
        if (uChanged and LVIF_STATE) <> 0 then
        begin
          if ((uNewState and LVIS_STATEIMAGEMASK) shr 12) <> ((uOldState and LVIS_STATEIMAGEMASK) shr 12) then
          begin
            listItem := listView1.Items[iItem];
            if listItem.StateIndex >= 2 then
              ListView1ItemChecked(listItem);
          end;
        end;
      end;
    end;
  end;
  //original ListView message handling
  OriginalListViewWindowProc(Message) ;
end;

procedure TForm1.ListView1AdvancedCustomDrawItem(Sender: TCustomListView;
  Item: TListItem; State: TCustomDrawState; Stage: TCustomDrawStage;
  var DefaultDraw: Boolean);
begin
  if item.StateIndex >= 2 then
    ListView1.Canvas.Font.Color := clRed;
end;

end.


ぱっく  2010-03-28 17:41:09  No: 38123

>monaaさん、素早い切り返しに驚きました。お忙しいところ恐縮です。
まずご報告ですが、動作します!

■StateImages未設定 http://sakuratan.ddo.jp/imgboard/img-box/img20100327224430.png
一番上はクリックしてチェックを入れてみました。「絶対」以外のチェックON/OFF自由です。
>    item.Checked := not Item.Checked; //ここがミソ
この部分を1つコメントアウトしてみると、「絶対」以外のチェック状態が反転します。
2つともコメントアウトしてみると、絶対チェックのチェックボックスが消滅します(?)
1回クリックすれば出現します。

■StateImages設定後 http://sakuratan.ddo.jp/imgboard/img-box/img20100327224545.png
残念ながらチェックボックス画像がこの状態のまま変化しません…。
>ご自分でチェックイメージを作成するなら、もう少し簡単かもしれません。
いえ、初めから自作画像を使っております…イメージとは画像のことですよね?

作っていただいたこのサンプルは私の理解をはるかに超えています。
ListViewWindowProcExというのがOnItemCheckedを追加するためというのは想像できるのですが、
・ listView1.Items[iItem]; ←このiItemってどこからやって来たのでしょう??
・ Item.Checkedは初期設定で必ずFalseになるはずなのに、なぜFalseとTrueが交互に並ぶのか??
分からないことだらけです。

コードの一部を変更して所望する動作を得ようと試行錯誤しましたが、例外エラーが出てしまい、
うまく行きませんでした…。
せっかくのサンプルを活かせなくてすみません。


ぱっく  2010-03-28 17:42:42  No: 38124

>そもそもさん、ありがとうございました!
動きます! 期待していた動作とバッチリ合います!! 
現状はこんな感じです。
http://sakuratan.ddo.jp/imgboard/img-box/img20100328083657.png

当たり前かもしれませんが、もはやCheckboxesプロパティのTrue/Falseには影響されません。
ただ、当初は通常の並びのようにTForm1.FormCreateを頭に、TListView.MouseDownをその下に持って
きてしまい、コンパイルしても何も起きずエラーも出さず、という状態でしたのでかなり悩みました。
functionとかは先に置く必要がある、という記述を思い出し、並び替えたところ動き出しました!
私の変てこりんな「ResultSII」も考慮していただいて、本当に感謝感激です。

少し疑問があります。質問ばかりですみません。
・ TListView.MouseDownの位置はTForm1.FormCreateより前ならば良い、ということでしょうか。
・ overrideという手法(ですよね?)でなく、通常のOnMouseDownに入れることは出来るのでしょうか。

なお、ListViewのこういった使い方は私のような初心者ほど欲してると推測します。
ですので、皆さんからいただいた知恵を有効な資産として残すため、もう少しコードを汎用化してから
サンプルをアップしたいと思います。monaaさんから教えていただいた知恵もすでに盛り込んでます。
なんかこのトピックがプログラムリストだらけになっちゃいますが、いいですよね?


重要なのは  2010-03-29 08:50:03  No: 38125

TForm1 のクラス宣言よりも前(上)に TListview のクラス宣言を置くこと。
実現部の TListView.MouseDown と TForm1.FormCreate の位置関係はどうでもかまわない。
OnMouseDownハンドラ と MouseDownのoverrideは、どちらも長所短所があるので、どちらを使うかは場合によりけり、好みの問題とも言えるかな?


KHE00221  2010-03-30 10:07:12  No: 38126

>私の変てこりんな「ResultSII」も考慮していただいて、本当に感謝感激です。

個人的には [ResultSII] があまり良くないので...こんなのはどうかなと

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ImgList, ComCtrls, CommCtrl, StdCtrls;

type

  TListItem = class(ComCtrls.TListItem)
  private
  protected
    function GetChecked: Boolean;
    procedure SetChecked(Value: Boolean);
    function GetEnabled: Boolean;
    procedure SetEnabled(Value: Boolean);
    function GetStateIndex: Integer;
    procedure SetStateIndex(Value: Integer);
  public
  published
    property Checked: Boolean read GetChecked write SetChecked;
    property Enabled: Boolean read GetEnabled write SetEnabled;
    property StateIndex: Integer read GetStateIndex write SetStateIndex;
  end;

  TListView = class(ComCtrls.TListView);

  TForm2 = class(TForm)
    ListView1: TListView;
    ImageList1: TImageList;
    Button1: TButton;
    Button2: TButton;
    procedure ListView1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
      Rect: TRect; State: TOwnerDrawState);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

function TListItem.GetStateIndex;
begin
    Result := inherited StateIndex;
end;

procedure TListItem.SetStateIndex(Value: Integer);
begin
    inherited StateIndex := Value;

    if Value and 1 = 0 then
    begin
      inherited Checked := False;
    end
    else
    begin
      inherited Checked := True;
    end;

end;

function TListItem.GetEnabled: Boolean;
begin
    if StateIndex and 2 = 0 then Result := False else Result := True;
end;

procedure TListItem.SetEnabled(Value: Boolean);
begin
    if Value = True then
    begin
      if StateIndex = -1 then
      begin
        StateIndex := 2;
      end
      else
      begin
        StateIndex := StateIndex or 2;
      end;
    end
    else
    begin
      if StateIndex = -1 then
      begin
        StateIndex := 0;
      end
      else
      begin
        StateIndex := StateIndex and (255-2);
      end;
    end;
end;

function TListItem.GetChecked: Boolean;
begin
    Result := inherited Checked;
end;

procedure TListItem.SetChecked(Value: Boolean);
begin
    inherited Checked := Value;

    if Checked = True then
    begin
      if StateIndex = -1 then
      begin
        StateIndex := 1
      end
      else
      begin
        StateIndex := StateIndex or 1;
      end;
    end
    else
    begin
      if StateIndex = -1 then
      begin
        StateIndex := 0;
      end
      else
      begin
        StateIndex := StateIndex and (255-1);
      end;
    end;

end;

procedure TForm2.FormCreate(Sender: TObject);
var
    ListItem: TListItem;
begin
    ListItem := TListItem(ListView1.Items.Add);
    ListItem.Caption := 'NO 1';
    ListItem.Checked := False;
    ListItem.Enabled := False;

    ListItem := TListItem(ListView1.Items.Add);
    ListItem.Caption := 'NO 2';
    ListItem.Checked := True;
    ListItem.Enabled := False;

    ListItem := TListItem(ListView1.Items.Add);
    ListItem.Caption := 'NO 4';
    ListItem.Enabled := True;
    ListItem.Checked := True;

    ListItem := TListItem(ListView1.Items.Add);
    ListItem.Caption := 'NO 3';
    ListItem.Checked := False;
    ListItem.Enabled := True;

    ListView1.OwnerDraw := True;
    ListView1.ViewStyle := vsReport;

end;

procedure TForm2.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
  Rect: TRect; State: TOwnerDrawState);
var
    Bitmap: TBitmap;
begin
    if Item.Enabled = False then
    begin
      ListView1.Canvas.Font.Color := clGray;
    end;

    Bitmap := TBitmap.Create;
    Bitmap.PixelFormat := pf32bit;
    Bitmap.SetSize(16,16);

    ImageList1.GetBitmap(Item.StateIndex,Bitmap);
    Sender.Canvas.TextRect(Rect,Rect.Left+20,Rect.Top,Item.Caption);
    Sender.Canvas.Draw(Rect.Left,Rect.Top,Bitmap);

    Bitmap.Free;

end;

procedure TForm2.ListView1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
    ListItem: TListItem;
begin
    ListItem := TListItem(ListView1.GetItemAt(X,Y));
    if Assigned(ListItem) = True then
    begin
      if htOnStateIcon in ListView1.GetHitTestInfoAt(X, Y) then
      begin
        if ListItem.Enabled = True then
        begin
          ListItem.Checked := not ListItem.Checked;
        end;
      end;
    end;
    Exit;
end;

end.


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加