ListView


ListView.Details

プロパティ

プロパティ 内容 既定値
bool VirtualMode trueならば、独自にデータを管理する仮想モード (virtual mode) となる false
ListView.ListViewItemCollection Items すべての項目を格納する、ListViewItemのコレクション  
ListView.ColumnHeaderCollection Columns すべての列ヘッダーのコレクション  
ListView.SelectedListViewItemCollection SelectedItems 選択されている項目が返され、それがなければ空のコレクションが返される

一方で選択されている項目を変更するには、ListViewItem.Selectedに設定する

 
ListView.SelectedIndexCollection SelectedIndices 選択されている項目のインデックスが返される  
ListViewItem FocusedItem 現在フォーカスがある項目。それがなければnull

たいてい最後に選択された項目がそれに該当するが、フォーカスがある項目が選択されているとは限らない。このプロパティは、最後にクリックされた項目を決定するのに用いる

 
ListViewItem TopItem コントロール内で、最初に表示されている項目  
bool AllowColumnReorder trueならば、ドラッグによる列の並べ替えを認める

これをプログラムで並べ替えるならば、ColumnHeader.DisplayIndexを指定する

false
bool FullRowSelect trueならば、項目が選択されたときに、すべてのサブ項目も選択する false
bool HideSelection trueならば、コントロールがフォーカスを失ったときは強調表示しない true
bool Scrollable trueならば、表示領域が不足したときにスクロールバーが表示される true
bool LabelWrap trueならば、必要なときテキストが改行して表示され、2行以上ならば切り詰められる。項目が選択されると、すべてのテキストが表示される

(このプロパティは、SmallIconとLargeIcon以外には適用されない)

true
bool CheckBoxes trueならば、チェックボックスが表示される false
bool MultiSelect trueならば、複数の項目を選択できる true
bool HoverSelection trueならば、マウス ポインターが項目の上に数秒間留まったときに、項目が自動的に選択される false
bool UseCompatibleStateImageBehavior trueならば、状態イメージ (state image) の動作が.NET Framework 1.1と互換性がある true
ImageList LargeImageList ViewがLargeIconのときに使用するImageList  
ImageList SmallImageList ViewがLargeIcon以外のときに使用するImageList  
Size TileSize ViewがTileのときの、タイルのサイズ  
ColumnHeaderStyle HeaderStyle ViewがDetailsのときの、列ヘッダーのスタイル Clickable
ListViewInsertionMark InsertionMark コントロール内で項目がドラッグされるとき、ドロップ位置を示す挿入マーク  
       
プロパティ - ListView Class (System.Windows.Forms) | Microsoft Learn

Items (項目)

ListView listView = new ListView();

listView.Items.Add("A");
listView.Items.Add("B");

ListViewItem item1 = listView.Items[0]; // {Text = "A"}
ListViewItem item2 = listView.Items[1]; // {Text = "B"}

項目の追加

項目は、ListViewItemCollection.Add()で追加できます。

項目を追加しても何も表示されないならば、ViewColumnsプロパティの設定を確認します。

項目の削除

メソッド 削除対象
ListViewItemCollection.Remove(ListViewItem) 指定項目
ListViewItemCollection.RemoveAt(Int32) 指定位置にある項目
ListViewItemCollection.RemoveByKey(String) 指定のキー (Nameプロパティの値) を持つ項目
ListViewItemCollection.Clear() すべて

ListViewItemCollectionから項目を削除すると、その項目のListViewプロパティはnullになります。またGroupプロパティもnullになり、それを含んでいたListViewGroupからはその項目が削除されます。

項目の選択

ListViewItem.Selectedをtrueに設定します。 ListViewItem.Selected プロパティ (System.Windows.Forms) | MSDN Selected - ListViewItem.cs

MultiSelectがtrueならば選択項目が追加され、falseならば選択項目が変更されます。

選択したときListViewにフォーカスがないとその状態が表示されないため、その必要があるならば選択と同時にフォーカスを設定します。解説 - ListViewItem.Selected プロパティ (System.Windows.Forms) | MSDN

listView.Items[0].Selected = true;
listView.Focus();
c# - How to select an item in a ListView programmatically? - Stack Overflow

項目の書式

項目はstringとして追加するため、ListBoxのように表示時に書式を変更できません。よって書式を設定してから追加します。

項目の非表示

項目を非表示にする方法は提供されていないため、その必要があるときはListView.Itemsからその要素を削除します。

SubItems (サブ項目)

ViewがDetailsまたはTileのとき、各項目に追加のテキスト、つまりサブ項目を表示できます。

SubItemsを表示するにはColumnsも設定します。そのときDetailsでは項目数+1以上、Tileでは2つのColumnHeaderが必要です。

サブ項目は追加のテキストを表示するだけのため、個別に選択することはできません。そのような用途ではDataGridViewを用います。

listView.Items[0].SubItems.Add("A");
listView.Items[0].SubItems.Add("B");

ListViewItem生成時に文字列の配列を渡すことで、サブ項目も同時に追加できます。

public ListViewItem(
    string[] items // 項目のサブ項目に表示するテキストの配列
)
ListViewItem(String[]) - ListViewItem Constructor (System.Windows.Forms) | Microsoft Learn

このときitems[0]の要素は、項目に表示されるテキストとして用いられます。

サブ項目の取得

SubItemsの最初の項目は、それを所有する項目と等値です。よって次の2つは異なるオブジェクトですが、同一の内容となります。

  • listView.Items[10].Text
  • listView.Items[10].SubItems[0].Text
ListViewItem item = listView.Items.Add("A");
string text1 = item.Text;             // "A"
string text2 = item.SubItems[0].Text; // "A"

bool equals = Object.ReferenceEquals(item, item.SubItems[0]); // false

サブ項目 (ListViewSubItem) のNameプロパティを設定すると、インデックスではなくそれをキーにサブ項目を取得できるようになります。

// 項目の追加
ListViewItem item0 = listView.Items.Add("A");
ListViewItem item1 = listView.Items.Add("B");

// サブ項目の追加
item0.SubItems.Add("10"); // このサブ項目のインデックスは1
item0.SubItems.Add("20"); // このサブ項目のインデックスは2
item0.SubItems.Add("30");


// サブ項目に名前を設定
item0.SubItems[1].Name = "x";
item0.SubItems[2].Name = "y"; // インデックス2のサブ項目を"y"とする

//  名前をキーにサブ項目を取得
ListViewItem.ListViewSubItem y
    = item0.SubItems["y"]; // {ListViewSubItem: {20}}

ListViewItem.ListViewSubItem z;
    = item0.SubItems["z"]; // null (キーに該当する項目がなければnull)

サブ項目の実体は、ListViewSubItemクラスです。ListViewItem.ListViewSubItem クラス (System.Windows.Forms) | MSDN

View (項目の表示方法)

列挙子  
View.Details※1
View.SmallIcon
View.LargeIcon
View.List
View.Tile※1
View 列挙型 (System.Windows.Forms) | MSDN

※1 DetailsまたはTileに設定するときは、必ずColumnsも設定します。さもなくばDetailsでは項目が何も表示されず、Tileではサブ項目が表示されません。解説 - ListView.View プロパティ (System.Windows.Forms)

Viewの指定によって作用するメンバが異なります。

作用するメンバ
ListViewメンバ View
Details SmallIcon LargeIcon List Tile
プロパティ Alignment      
AutoArrange      
Columns      
FullRowSelect        
Groups  
HeaderStyle        
InsertionMark    
LabelWrap      
メソッド AutoResizeColumn        
FindItemWithText    
FindNearestItem      
GetItemAt      
イベント DrawSubItem        

LargeIconでの項目の表示間隔の変更

画像の表示サイズはLargeImageListに指定するImageList.ImageSizeで変更できますが、個々の項目の表示間隔を変更する方法は提供されていません。しかしWin32のLVM_SETICONSPACINGメッセージを送ることで、アイコン表示のListViewのアイコン間のスペースを変更できます。 LVM_SETICONSPACING message (Commctrl.h) - Win32 apps | Microsoft Learn ListView_SetIconSpacing macro (commctrl.h) - Win32 apps | Microsoft Learn

public void ListView_SetIconSpacing(ListView listview, int cx, int cy)
{
    const int LVM_FIRST = 0x1000;
    const int LVM_SETICONSPACING = LVM_FIRST + 53;
    int lParam = cy << 16 | cx;

    IntPtr result = SendMessage(
        new HandleRef(listview, listview.Handle),
        LVM_SETICONSPACING,
        IntPtr.Zero,
        (IntPtr)lParam);

    // 設定前の値
    int previousX = (int)result & 0x0000FFFF;
    int previousY = (int)result >> 16;
}

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(HandleRef hWnd, int msg, IntPtr wParam, IntPtr lParam);
c# - Displaying thumbnail icons 128x128 pixels or larger in a grid in ListView - Stack Overflow

たとえばImageSizeが(100, 100)であるListViewの項目に外接する四角形の大きさを、ListView.GetItemRect(index, ItemBoundsPortion.Entire)で取得すると、

{X=0,Y=0,Width=168,Height=119} 左上端の項目
{X=168,Y=143,Width=168,Height=119} その右下に位置する項目

のようになります。これに対してLVM_SETICONSPACINGで(200,200)の間隔に指定すると、

{X=0,Y=0,Width=200,Height=119} 左上端の項目
{X=200,Y=200,Width=200,Height=119} その右下に位置する項目

となります。結果として表示間隔は指定したとおりになっていますが、項目の領域の幅は変化するが、高さは変化していないことがわかります。これを高さも含めて指定の大きさにしたいならば、ViewをTileにしてTileSizeで指定します。

Columns (列)

列の追加

サブ項目も含めた項目を表示するのに必要な数だけ追加します。列が不足する場合にはその項目の内容が表示されず、超過する場合には空欄となります。

public virtual ColumnHeader Add(
    string text,                  // 列ヘッダのテキスト
    int width,                    // 列ヘッダの最初の幅
    HorizontalAlignment textAlign // 水平方向の配置位置
)
ListView.ColumnHeaderCollection.Add メソッド (String, Int32, HorizontalAlignment) (System.Windows.Forms) | MSDN

初期化する項目の違いにより、下表のオーバーロードがあります。widthを省略すると60に、textAlignを省略するとHorizontalAlignment.Leftとなります。

メソッド キー テキスト 配置位置 画像の
インデックス
画像の
キー
Add(String)          
Add(String, Int32)        
Add(String, Int32, HorizontalAlignment)      
Add(String, String)        
Add(String, String, Int32)      
Add(String, String, Int32, HorizontalAlignment, Int32)  
Add(String, String, Int32, HorizontalAlignment, String)  
ListView.ColumnHeaderCollection.Add メソッド (System.Windows.Forms) | MSDN
listView.Columns.Add("A");
listView.Columns.Add("B", 100);
listView.Columns.Add("C", 60, HorizontalAlignment.Right);
列の幅

列の幅はColumnHeader.Widthプロパティにより設定されますが、そこでは指定値の下位16ビットの値のみが使用されるため、負数を指定しないようにします。 Add - ListView.cs Width - ColumnHeader.cs MAKELPARAM - NativeMethods.cs

行の高さ

列の幅は列ヘッダの幅で指定できますが、行の高さを指定する方法は提供されていません。しかし行の高さはImageListが収まる高さに拡張されるため、それを利用すれば高さを変更できます。ただしこのImageListのサイズは256x256が上限のため、指定できる高さも256までとなります。C# Change ListView Item's/Row's height - Stack Overflow

ImageList images = new ImageList();
images.ImageSize = new Size(1, 100);

listView.SmallImageList = images;
ヘッダ行の高さ

ヘッダの高さは、LVM_GETHEADERメッセージで得られるヘッダのハンドルを、GetWindowRect()へ渡すことで取得できます。c# - How do I get the header height of a Listview - Stack Overflow

一方で、ヘッダの高さを変更する手段は提供されていません。

列の並べ替え

ColumnHeader.DisplayIndexに表示位置を指定することで、列を並べ替えられます。ColumnHeader.DisplayIndex プロパティ (System.Windows.Forms) | MSDN

たとえば現在1番目に表示されて項目を0番目に移動するには、次のようにします。

listView.Columns[1].DisplayIndex = 0;

一方で並べ替えをユーザーに委ねるならば、AllowColumnReorderプロパティをtrueにします。

列の非表示

非表示にする機能は提供されていないため、代替の手段を講じる必要があります。幅を0に設定するのが最も簡単な解法で、次のようにします。

int target = 1;
ColumnHeader columnHeader = listView.Columns[target];

if (columnHeader.Width != 0)
{
    // 幅を0にする
    columnHeader.Width = 0;
}
else
{
    // 列ヘッダの幅に合わせて、幅を再設定する
    columnHeader.AutoResize(ColumnHeaderAutoResizeStyle.HeaderSize);
}

またはListView.ColumnsからColumnHeaderを削除すれば、列を非表示にしたのと同じ状態となります。

listView.Columns.RemoveAt(1);

そのとき存在する項目のサブ項目は適切に処理されますが、その後に項目を追加するとサブ項目と列ヘッダの対応関係が一致しなくなります。そのような場合には削除対象とする列ヘッダを末尾に配置しておけば、問題を回避できます。配置位置は末尾でも、表示位置はDisplayIndexで変更できます。

listView.Columns.RemoveAt(listView.Columns.Count - 1);

Groups (グループ)

Groupsを機能させるには、ViewはList以外とします。

// グループを作成する
ListViewGroup group1 = listView.Groups.Add("", "Group1");
ListViewGroup group2 = listView.Groups.Add("", "Group2");

// 項目を作成する
ListViewItem item1 = new ListViewItem("Item1", group1);
ListViewItem item2 = new ListViewItem("Item2", group1);
ListViewItem item3 = new ListViewItem("Item3", group2);

listView.Items.AddRange(new[] { item1, item2, item3 });

グループに割り当てていない項目、または割り当てているがそのグループがListViewに追加されていない項目は、"Default"という名前の既定のグループに割り当てられます。

グループはListViewGroupのインスタンスごとに分類されるため、 HeaderやNameプロパティの値が重複しても構いません。

コンストラクタのListViewGroup(string header)ListViewGroup(string key, string headerText)の、headerheaderTextは同じであり、Headerプロパティに設定されます。またkeyはNameプロパティの値となります。ListViewGroup - ListViewGroup

グループを非表示にしたり折りたたんだりする機能は提供されていないため、その必要があるときにはグループに割り当てた項目をすべて削除するなどします。c# - ListView hide or collapse selected group - Stack Overflow
.NET 5以降ならば、CollapsedStateプロパティをCollapsedとすることで折りたためます。

グループのヘッダーテキスト (グループの最初に表示されるテキスト) のフォントは変更できず、ListView.OwnerDrawをtrueとしても、グループを描画するためのイベントがありません。winforms - C# change ListViewGroup header text to bold - Stack Overflow

Sorting (項目の並べ替え)

SortingプロパティをSortOrder.AscendingまたはSortOrder.Descendingに設定すると、項目のテキストのアルファベット順または逆順に並べ替えられます。その状態で項目を追加するとそれも自動で並べ替えられますが、項目のテキストを変更した場合は並べ替えられません。またサブ項目は評価の対象となりません。ListView.Sorting プロパティ (System.Windows.Forms) | MSDN

より柔軟に並べ替えるには、ListViewItemSorterを用います。

この機能はItemsプロパティのコレクション自体を並べ替えるため、並べ替える前の状態へは戻せません。

外観

並べ替えの状態をグリフなどのアイコンで示したいならば、独自に実装します。listview - How to I display a sort arrow in the header of a list view column using C#? - Stack Overflow

ViewがView.Detailsのとき並べ替えの機能を実装しないならば、HeaderStyleプロパティをColumnHeaderStyle.Nonclickableにして、クリックに応答しないことがわかるようにします。このプロパティは既定でClickableとなっておりクリックできるように振る舞いますが、ColumnClickイベントに応じなければ何も起きません。

グループの並べ替え

GroupsIListを継承したコレクションであるため、それの操作と同様の方法で並べ替えられます。

ListViewGroup group = listView.Groups[1]; // インデックス1の項目を取得
listView.Groups.Remove(group);            // それをコレクションから削除
listView.Groups.Insert(0, group);         // それをインデックス0の位置に挿入

ListViewItemSorter

IComparerインターフェイスを実装したオブジェクトを設定することで、そのCompare()メソッドの結果によって並べ替えられます。なおListViewItemSorterの型はIComparerのため、これのジェネリック版であるIComparer<T>を用いることはできません。

並べ替えが有効の状態では、ListViewItemCollection.Add()で項目を追加するたびに並べ替えが実行されます。よって性能が問題になるならば、ListViewItemCollection.AddRange()で一括して追加するようにします。

たとえば特定の列のサブ項目のテキストで比較するクラスを、次のように定義します。

internal class ListViewItemComparer : System.Collections.IComparer
{
    private int column;

    public ListViewItemComparer(int column)
    {
        this.column = column;
    }

    public int Compare(object x, object y)
    {
        return String.Compare(
            ((ListViewItem)x).SubItems[this.column].Text,
            ((ListViewItem)y).SubItems[this.column].Text);
    }
}

そしてColumnClickイベントのハンドラでそのオブジェクトをListViewItemSorterプロパティに設定することで、その条件でSort()メソッドが呼び出されます。

private void ListView_ColumnClick(object sender, ColumnClickEventArgs e)
{
    listView.ListViewItemSorter = new ListViewItemComparer(e.Column);
}

昇順、降順を切り替え式にする

並べ替え方法の状態をフィールドで保持し、列の変更前の状態によりそれを切り替えるようにします。

internal class ListViewItemComparer : System.Collections.IComparer
{
    private int column = 0;
    private SortOrder sortOrder = SortOrder.Ascending;

    public int Column
    {
        get { return this.column; }

        set
        {
            if (this.column == value)
            {
                // 同じ列を指定されたならば、昇順・降順を切り替える
                this.sortOrder = (this.sortOrder == SortOrder.Ascending) ? SortOrder.Descending : SortOrder.Ascending;
            }
            else
            {
                this.column = value;
                this.sortOrder = SortOrder.Ascending;
            }
        }
    }

    public int Compare(object x, object y)
    {
        int result = String.Compare(
            ((ListViewItem)x).SubItems[this.column].Text,
            ((ListViewItem)y).SubItems[this.column].Text);

        return (this.sortOrder == SortOrder.Ascending) ? result : -result;
    }
}

使用時にはこのオブジェクトを初期化時にListViewItemSorterプロパティに設定し、ColumnClickイベントでは列の変更を指示します。

public MyForm()
{
    InitializeComponent();

    listView.ListViewItemSorter = new ListViewItemComparer();
}

private void ListView_ColumnClick(object sender, ColumnClickEventArgs e)
{
    ((ListViewItemComparer)listView.ListViewItemSorter).Column = e.Column;
    listView.Sort(); // Sort()を明示的に呼び出す
}

LargeImageList

項目にアイコンを表示するときの、ImageListを設定します。この中のどの画像を個々の項目に用いるかは、ListViewItem.ImageIndexで指定します。ListView.LargeImageList プロパティ (System.Windows.Forms)

Visual StudioのGUIからこれを設定するには、ツールボックスからImageListのコンポーネントを追加し、それをプロパティに設定します。

Activation

項目をマウスでアクティブにするための条件を設定します。キーボードにはこの設定は作用せず、つねにEnterです。

メンバ アクティブにする方法 マウスオーバー時の動作 ラベルの編集
OneClick シングルクリック カーソル形状が変わり、項目のテキストの色が変わる 不可
Standard [既定] ダブルクリック ない 可能
TwoClick 任意の間隔のダブルクリック 項目のテキストの色が変わる 不可
ItemActivation 列挙型 (System.Windows.Forms) | MSDN

Scrollable

trueのとき、表示領域が不足したときにスクロールバーが表示されます。表示されるスクロールバーはViewの設定によって異なり、下表のようになります。

Viewの値 スクロールバー
水平 垂直
View.Details
View.SmallIcon
View.LargeIcon ×
View.List ×
View.Tile

View.Listの形式で垂直スクロールバーを表示させるには、View.Detailsに変更し列ヘッダを非表示にします。c# - Making ListView scrollable in vertical direction - Stack Overflow

listView.View = View.Details;
listView.Columns.Add("");
listView.HeaderStyle = ColumnHeaderStyle.None;

ただし列ヘッダを非表示とすると、内容がその列の幅に収まらないときに全体が表示されなくなります。これに対処するには、

listView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);

として、列の幅をその内容に応じて調整します。このメソッドは、ListViewとそれが含まれるFormが構築された後に呼び出します。Remarks - ListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle) Method (System.Windows.Forms) | Microsoft Learn

なお列の幅は、ColumnHeader.Widthに-1 (LVSCW_AUTOSIZE) や-2 (LVSCW_AUTOSIZE_USEHEADER) を設定することでも自動調整させられます。注釈 - ColumnHeader.Width プロパティ (System.Windows.Forms) | Microsoft Learn

TopItem

ViewがDetailsでGroupsが有効なときは、このプロパティはつねにItems[0]の項目を返します。 c# - .NET: TopItem property of ListView fails with "ShowGroups" = true - Stack Overflow Possible bug in ListView control: TopItem property doesn't work with groups

このプロパティから取得するときはLVM_GETTOPINDEXメッセージで得られるインデックスにあるItemsプロパティの要素が返されるだけであり、このメッセージはViewがDetailsでGroupsが有効なときは0を返すとあることから、これが仕様です。TopItem - ListView.cs

LabelEdit

ActivationがStandardのときにこのプロパティをtrueにすると、ユーザーが項目のテキストを編集できるようになります。ただしサブ項目は編集できません。ListView.LabelEdit プロパティ (System.Windows.Forms) | MSDN

TileSize

ViewがTileのときのタイルのサイズを指定できます。ListView.TileSize プロパティ (System.Windows.Forms) | Microsoft Learn

指定したSize構造体の幅および高さが0であったり、いずれかが0以下だと、「TileSize は正の値でなければなりません。」としてArgumentOutOfRangeExceptionが投げられます。TileSize - ListView.cs

ListViewの表示が確定後に項目の配置が変化するほどにサイズを変更すると配置が乱れることがありますが、Itemsプロパティを再設定することで回避できます。

listView.TileSize = 新しいサイズ

ListViewItem[] items = new ListViewItem[listView.Items.Count];
for (int i = 0; i < items.Length; i++)
{
    items[i] = new ListViewItem(listView.Items[i].Text);
}

listView.Items.Clear(); // 消去して
listView.Items.AddRange(items); // 再設定する

メソッド

メソッド 機能
Clear() すべての項目と列を削除する。ただしGroupsは削除されないため、それはListViewGroupCollection.Clear()で削除する必要がある

列は残して項目だけを削除するには、ListViewItemCollection.Clear()を呼ぶ

Sort() 項目を並べ替える

一般的に、並べ替えはSortingプロパティに設定することで自動で行われる。このメソッドは並べ替えの条件を変更した場合に呼ぶ

EnsureVisible(Int32) 指定位置の項目が可視となるように、コントロールの内容をスクロールする
   
メソッド - ListView Class (System.Windows.Forms) | Microsoft Learn

FindItemWithText()

指定のテキストから始まる項目を、項目やサブ項目から見つけられます。

public ListViewItem FindItemWithText(
    string text,
    bool includeSubItemsInSearch,
    int startIndex // 検索を開始する項目のインデックス
)
ListView.FindItemWithText メソッド (String, Boolean, Int32) (System.Windows.Forms) | MSDN

これをキーで見つけるには、ListViewItem.Nameプロパティで名前を指定しておき、ListView.ListViewItemCollection.Find()で検索します。

HitTest()

指定の座標位置にある項目の情報を得られます

public System.Windows.Forms.ListViewHitTestInfo HitTest (
    System.Drawing.Point point // 項目の情報を取得する位置。クライアント座標 (コントロールの左上隅が基準)
    );
HitTest(Point) - ListView.HitTest メソッド (System.Windows.Forms) | Microsoft Learn
Point position = listView.PointToClient(Control.MousePosition);
ListViewHitTestInfo info = listView.HitTest(position);

指定位置がコントロールから外れているかグループのヘッダーテキスト上にあると、ListViewHitTestInfoのLocationはNone、ItemとSubItemはnullとなります。

FindNearestItem()

ViewがSmallIconまたはLargeIconのとき、指定の座標位置の次にある項目を見つけられます。

public ListViewItem FindNearestItem(
    SearchDirectionHint dir, // 検索する方向
    Point point              // 検索を開始する位置
)
ListView.FindNearestItem メソッド (SearchDirectionHint, Point) (System.Windows.Forms) | MSDN

dirに指定できるのは、SearchDirectionHintのLeft、Up、Right、Downの4つです。

GetItemAt()

指定位置にある項目を得られます。

public System.Windows.Forms.ListViewItem GetItemAt (int x, int y);
ListView.GetItemAt(Int32, Int32) Method (System.Windows.Forms) | Microsoft Learn

情報はHitTest()と同様にLVM_HITTESTメッセージで取得するため、指定位置にグループのヘッダーテキストがあるとnullが返されます。

GetItemRect()

指定項目の指定部分に外接する四角形を取得できます。ListViewItem.GetBounds(ItemBoundsPortion)からも取得できますが、これは内部でGetItemRect()を呼んでおり同じ結果が得られます。GetBounds - ListViewItem.cs

public System.Drawing.Rectangle GetItemRect (
    int index,
    System.Windows.Forms.ItemBoundsPortion portion
    );
ListView.GetItemRect Method (System.Windows.Forms) | Microsoft Learn

項目に外接する四角形はListViewItem.Boundsプロパティから取得できます。これは内部でListView.GetItemRect(Index)を呼んでおり、ItemBoundsPortion.Entireで全体を取得するのと同じです。Bounds - ListViewItem.cs

サブ項目だけの部分はItemBoundsPortionでは指定できないため、ListViewSubItem.Boundsプロパティから取得します。

EnsureVisible()

ListViewItemが既知ならば、ListViewItem.EnsureVisible()を呼んでも同じです。EnsureVisible - ListViewItem.cs

指定の項目がすでに表示されているならば、スクロールされません。これを指定量だけ確実にスクロールさせるには、LVM_SCROLLメッセージを送ります。

ProcessCmdKey()

複数列で表示する形式でキーで操作するとき、View.Listではインデックス順でフォーカスを移動させられますが、それ以外のView.SmallIconやView.Tileでは端の列から次のインデックスへ移動しません。これをキーで移動するようにするには、ProcessCmdKey()でフォーカスを設定します。

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    if (keyData == Keys.Right && this.FocusedItem != null)
    {
        int nextIndex = this.FocusedItem.Index + 1;
        if (nextIndex < this.Items.Count)
        {
            ListViewItem item = this.Items[nextIndex]
            item.Focused = item.Selected = true;
        }
        return true;
    }
    return base.ProcessCmdKey(ref msg, keyData);
}

イベント

イベント 発生タイミング
EventHandler ItemActivate 項目がアクティブになったとき
EventHandler SelectedIndexChanged SelectedIndicesプロパティが変更されたとき。プログラムによる変更時にも発生する
ListViewItemSelectionChangedEventHandler ItemSelectionChanged 項目の選択状態が変更されたとき。選択されていない状態から選択されている状態への変更、そしてその逆の状態への変更でも発生する
EventHandler TextChanged Textプロパティが変更されたとき
ColumnClickEventHandler ColumnClick ユーザが列ヘッダをクリックしたとき
ColumnWidthChangingEventHandler ColumnWidthChanging 列の幅が変更されるとき。ヘッダーの境界がドラッグされている間、連続して発生する
ColumnWidthChangedEventHandler ColumnWidthChanged 列の幅が首尾よく変更されたとき。ヘッダーの境界のドラッグが終了し、マウスボタンが離されたときに発生する
ListViewItemMouseHoverEventHandler ItemMouseHover マウスポインタが項目の上で停止したとき。FullRowSelectがtrueならばサブ項目も含めた全体、さもなくば項目のテキストの上だけが対象
PaintEventHandler Paint ListViewでは発生しない
     
イベント - ListView Class (System.Windows.Forms) | Microsoft Learn リスト ビュー メッセージ - Win32 apps | Microsoft Learn

ItemActivate

アクティブになる条件はActivationプロパティに依存します。

Clickイベントはキー入力では発生しないため、ListViewではこのItemActivateで処理します。このイベントはアクティブになった項目の情報を伝えないため、SelectedIndicesかSelectedItemsプロパティからそれを取得します。ListView.ItemActivate イベント (System.Windows.Forms) | Microsoft Learn

グループのヘッダーテキストがクリックされ、グループのすべての項目が選択された状態になっても、このイベントは発生しません。これを発生するようにするには既定のOnItemActivate()を無効にした上で、OnSelectedIndexChanged()でOnItemActivate()を呼ぶようにします。

private bool keyDowned = false;
private bool selectAllInProgress = false;

protected override void OnItemActivate(EventArgs e)
{
    // 既定のイベントを無効にする
    // base.OnItemActivate(e);
}

protected override async void OnSelectedIndexChanged(EventArgs e)
{
    base.OnSelectedIndexChanged(e);
    
    // キーの押下またはSelectAll()の処理中ならば、何もしない
    if (this.keyDowned || this.selectAllInProgress) return;

    int count = this.SelectedItems.Count;
    if (count != 0)
    {
        const int WAIT_FOR_SELECTION_TO_BE_FIXED = 100;
        await System.Threading.Tasks.Task.Delay(WAIT_FOR_SELECTION_TO_BE_FIXED).ConfigureAwait(true);

        if (count == this.SelectedItems.Count) // 変更が確定した
        {
            base.OnItemActivate(e);
        }
    }
}

protected override void OnKeyDown(KeyEventArgs e)
{
    base.OnKeyDown(e);

    if (e.KeyData == Keys.Enter)
    {
        base.OnItemActivate(EventArgs.Empty);
    }
    else
    {
        this.keyDowned = true;
    }
}

protected override void OnKeyUp(KeyEventArgs e)
{
    base.OnKeyUp(e);
    this.keyDowned = false;
}

public override void SelectAll()
{
    this.selectAllInProgress = true; // SelectedIndexChangedイベントを抑制する
    base.SelectAll();
    this.selectAllInProgress = false;

    OnSelectedIndexChanged(EventArgs.Empty);
}

SelectedIndexChanged

このイベントはSelectedIndicesが変更されたときに発生するため、そのたびに選択されている項目の数が変化します。たとえば3つの項目が選択されている状態で、それ以外の項目を1つ選択すると4回発生し、そのたびにListView.SelectedItems.Countは2→1→0→1と変化します。Remarks - ListView.SelectedIndexChanged Event (System.Windows.Forms) | Microsoft Learn

よってこのイベント発生時点で選択されている項目を処理すると処理量が膨大になることがあるため、ItemSelectionChangedで個別に処理するようにします。または一定時間待ち、選択が確定したことを確認します。

private async void ListView_SelectedIndexChanged(object sender, EventArgs e)
{
    int count = listView.SelectedIndices.Count;

    await Task.Delay(100);
    if (count == listView.SelectedIndices.Count)
    {
        // 選択が確定された
    }
}

ItemSelectionChanged

このイベントは、個々の項目の選択状態が変更されるたびに発生します。よって複数の項目が選択または選択解除された場合には、その項目の数だけイベントが発生します。

変更が選択か解除かは、引数のListViewItemSelectionChangedEventArgs.IsSelectedで判別できます。

MouseClick

MouseClickは、項目がクリックに応答する範囲内でしか発生しません。一方でMouseDownは、その範囲外とグループのヘッダーテキストでも発生します。ただし両方とも、列ヘッダへのクリックでは発生しません。

MouseUp

項目をマウスボタンで押下したときは、MouseUpが発生した時点で選択された状態となっています。一方でグループのヘッダーテキストでは、マウスボタンを離すタイミングによっては対象の項目が選択される前の状態であることがあります。

ColumnClick

列ヘッダのクリックはこのColumnClickで検出できますが、右クリックはListViewを拡張してウィンドウ メッセージから判定する必要があります。

protected override void WndProc(ref Message m)
{
    base.WndProc(ref m);

    const int WM_CONTEXTMENU = 0x007B;
    if (m.Msg == WM_CONTEXTMENU)
    {
        IntPtr handle = m.WParam;
        if (handle != this.Handle) // ユーザーが右クリックしたウィンドウへのハンドルと、ListViewのハンドルが異なる
        {
            // ヘッダが右クリックされた
        }
        else
        {
            // ヘッダ以外が右クリックされた
        }

        int param = m.LParam.ToInt32();
        int x = param & 0x0000FFFF;
        int y = param >> 16;

        Point clickLocation = this.PointToClient(new Point(x, y));
    }
}
c# - ListView ContextMenuStrip for column headers - Stack Overflow WM_CONTEXTMENU メッセージ (Winuser.h) - Win32 apps | Microsoft Learn

ColumnWidthChanging

ハンドラの引数のColumnWidthChangingEventArgs.Cancelをtrueとすることで列の幅が変更されていないように表示されますが、ドラッグが終了すると、そのときに指定された幅が適用されてしまいます。よって指定された幅を無効とするには、ColumnWidthChangedにも応答して適正な値に修正します。

このイベントが発生しないときは、OSを再起動してみます。

DrawItem

項目の描画に、オーナー描画 (owner drawing / オーナードロー / カスタム描画) が必要なときに発生します。ListView.DrawItem Event (System.Windows.Forms) | Microsoft Learn

OwnerDrawプロパティをtrueとしたならば、DrawItemに応答し描画せねばなりません。さもなくば項目が何も表示されなくなります。

ViewがDetailsの場合

ViewがDetailsのときは、項目や列ヘッダの描画にはDrawSubItem、DrawColumnHeaderイベントが発生します。よってこのDrawItemでは、背景などのすべての項目に共通の描画をします。

DrawItemイベントは基になっているWin32コントロールのバグのため、ViewがDetailsのときマウス ポインタが移動すると、初回はDrawSubItemを伴わずにDrawItemだけが発生します。その結果DrawItemで背景が描画されると、DrawSubItemの内容が描画されていない状態となります。この問題を回避するにはMouseMoveイベントで項目を再描画させるか、ColumnIndexが0のときに行全体を描画するようにします。Remarks - ListView.DrawItem Event (System.Windows.Forms) | Microsoft Learn

private void ListView_DrawItem(object sender, DrawListViewItemEventArgs e)
{
    e.DrawFocusRectangle(); // フォーカスがなければ描画されない DrawFocusRectangle - DrawListViewItemEventArgs.cs

    // View.DetailsならばDrawSubItemイベントで描画するため、ここでは描画しない
    if (listView.View != View.Details)
    {
        e.DrawText();
    }
}

private void ListView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
    //e.DrawDefault = true; // フォーカス四角形も描画される
    e.DrawText();
}

private void ListView_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
{
    e.DrawDefault = true;
}
c# - Default implementation for ListView OwnerDraw - Stack Overflow
DrawSubItem

名前からはサブ項目の描画時のみ発生しそうですが、インデックス0の列の描画でも発生するため、項目も対象となります。ListView.DrawSubItem Event (System.Windows.Forms) | Microsoft Learn

DrawItemのハンドラでDrawDefaultをtrueにすると、このDrawSubItemは発生しません。また描画すべきサブ項目が存在しない項目の列でも、発生しません。

DrawFocusRectangle()

このメソッドはViewがDetailsのとき、UpdateBounds()により左端の座標が4ピクセル正方向へ移動して描画されるため、DrawListViewItemEventArgs.Boundsの範囲と合わなくなります。これが問題となるならばこのメソッドを呼ばず、ControlPaint.DrawFocusRectangle()で描画します。DrawFocusRectangle - DrawListViewItemEventArgs.cs

表示位置を変更する場合

Columns[0]の項目はColumnHeader.TextAlignで表示位置を指定してもそれが反映されず、e.DrawText()ではつねに左端に描画されてしまいます。 Remarks - ColumnHeader.TextAlign Property (System.Windows.Forms) | Microsoft Learn Listview with OwnerDraw and AllowColumnReorder problem

この問題に対処するには、正しい位置を計算し直した上で描画します。

private void ListView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
    ListView.ColumnHeaderCollection columns = listView.Columns;
    if (e.ColumnIndex != 0 || columns[0].DisplayIndex == 0)
    {
        e.DrawText();
    }
    else
    {
        int prevDisplayIndex = columns[0].DisplayIndex - 1;

        int index;
        for (index = 0; index < columns.Count; index++)
        {
            if (columns[index].DisplayIndex == prevDisplayIndex) break;
        }

        // すぐ左に表示されるサブ項目の右端を、描画領域の左端に設定する
        Rectangle bounds = e.Bounds;
        bounds.X = e.Item.SubItems[index].Bounds.Right;

        TextRenderer.DrawText(e.Graphics, e.SubItem.Text, this.Font, bounds, this.ForeColor, TextFormatFlags.EndEllipsis);
    }
}
c# - ListView OwnerDraw with AllowColumnReorder don't work correct - Stack Overflow

選択されている項目の背景色を変更する

オーナー描画をしないときには、選択されている項目は背景色が変更され、他と区別できるようになっています。

private void ListView_DrawItem(object sender, DrawListViewItemEventArgs e)
{
    if (e.Item.Selected && listView.Focused) // 項目が選択され、ListViewにフォーカスがある
    {
        using (Brush brush = new SolidBrush(SystemColors.Highlight))
        {
            e.Graphics.FillRectangle(brush, e.Bounds);
        }
    }

    e.DrawFocusRectangle();
}

private void ListView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
{
    // 背景色を変更しているため、テキストの前景色も変更する
    Color color = e.Item.Selected ? SystemColors.HighlightText : listView.ForeColor;
    if (e.ColumnIndex == 0)
    {
        e.Item.ForeColor = color;
    }
    else
    {
        e.SubItem.ForeColor = color;
    }

    e.DrawText();
}

DrawListViewSubItemEventArgs.ItemStateは、NMCUSTOMDRAWのuItemStateを表しています。new DrawListViewSubItemEventArgs - ListView.cs

そしてHideSelectionがfalseのとき、このuItemStateはCDIS_SELECTEDを正しく表さないため、項目が選択されているかどうかはDrawListViewSubItemEventArgs.Item.Selectedで確認します。Members - NMCUSTOMDRAW (commctrl.h) - Win32 apps | Microsoft Learn

マウスホバー時のちらつきを抑制

WndProc()をオーバーライドし、msg=0x204e (WM_REFLECT + WM_NOTIFY) でNM_CUSTOMDRAWのとき、WM_PAINTから呼ばれたものでなければそのメッセージを破棄することで、ちらつきを抑制できます。c# - Flickering in listview with ownerdraw and virtualmode - Stack Overflow

public const int WM_USER = 0x0400;
public const int WM_CREATETIMER = WM_USER + 1;
public const int WM_KILLTIMER = WM_USER + 2;
public const int WM_REFLECT = WM_USER + 0x1C00;
WM_REFLECT - NativeMethods.cs WM_NOTIFY message (Winuser.h) - Win32 apps | Microsoft Learn
#define NM_FIRST                (0U-  0U)       // generic to all controls
...
#define NM_CUSTOMDRAW           (NM_FIRST-12)
CommCtrl.h NMHDR (richedit.h) - Win32 apps | Microsoft Learn NM_CUSTOMDRAW notification code (Commctrl.h) - Win32 apps | Microsoft Learn

もしくは簡単には、WM_PAINTメッセージ以外ではオーナー描画をしないようにします。In my case, I overrode each OnDrawXXX function... - c# - Flickering in listview with ownerdraw and virtualmode - Stack Overflow

スクロールの検知

スクロールを通知するイベントは用意されていないため、ListViewを継承して実装します。

class MyListView : ListView
{
    public event ScrollEventHandler Scroll;


    protected virtual void OnScroll(ScrollEventArgs e)
    {
        // Scrollイベントを発生する
        if (Scroll != null) Scroll(this, e);
    }

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        const int WM_VSCROLL = 0x115;
        const int WM_MOUSEWHEEL = 0x20A;
        const int WM_KEYDOWN = 0x100;

        ScrollEventType scrollEventType;
        switch (m.Msg)
        {
            case WM_VSCROLL:    // 垂直スクロールバーの操作によるスクロール
            case WM_MOUSEWHEEL: // マウスホイールの操作によるスクロール
                scrollEventType = (ScrollEventType)(m.WParam.ToInt32() & 0xFFFF);
                break;

            case WM_KEYDOWN: // キー操作によるスクロール
                switch (m.WParam.ToInt32())
                {
                    case (int)Keys.Down:
                    case (int)Keys.Up:
                        scrollEventType = ScrollEventType.SmallDecrement;
                        break;

                    case (int)Keys.PageDown:
                    case (int)Keys.PageUp:
                        scrollEventType = ScrollEventType.LargeDecrement;
                        break;

                    case (int)Keys.Home:
                        scrollEventType = ScrollEventType.First;
                        break;

                    case (int)Keys.End:
                        scrollEventType = ScrollEventType.Last;
                        break;

                    default:
                        return;
                }
                break;

            default:
                return;
        }


        const int SB_HORZ = 0; // 水平スクロールバー
        const int SB_VERT = 1; // 垂直スクロールバー

        const int SIF_RANGE = 0x1;
        const int SIF_PAGE = 0x2;
        const int SIF_POS = 0x4;
        const int SIF_TRACKPOS = 0x10;
        const int SIF_ALL = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS;

        SCROLLINFO si = new SCROLLINFO();
        si.fMask = SIF_ALL;
        si.cbSize = (uint)Marshal.SizeOf(si);

        GetScrollInfo(m.HWnd, SB_VERT, ref si);

        int newValue = si.nPos; // スクロールバーの新しい値
        OnScroll(new ScrollEventArgs(scrollEventType, newValue));
    }

    [StructLayout(LayoutKind.Sequential)]
    struct SCROLLINFO
    {
        public uint cbSize;
        public uint fMask;
        public int nMin;   // 最小のスクロール位置
        public int nMax;   // 最大のスクロール位置
        public uint nPage; // デバイス単位でのページ サイズ
        public int nPos;   // スクロール ボックスの位置
        public int nTrackPos; // ユーザーがドラッグしているスクロール ボックスの、当面の位置
    }

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetScrollInfo(IntPtr hwnd, int nBar, ref SCROLLINFO lpsi);
}

列の追加の検知

列が追加されたことは、LVM_INSERTCOLUMNメッセージで確認できます。LVM_INSERTCOLUMN メッセージ (Commctrl.h) - Win32 apps | Microsoft Learn

コレクション エディターによるプロパティの設定

Visual Studioでコントロールのプロパティを開き、その下部にある

  • 項目の編集...
  • 列の編集...
  • グループの編集...

をクリックします。

項目の編集

Textプロパティを編集できないなど、期待する操作をできないときには、Visual Studioを再起動してみます。方法 : デザイナーで Windows フォーム ListView コントロールを使って項目を追加および削除する | MSDN

列の編集

方法 :デザイナーを使って Windows フォーム ListView コントロールに列を追加する | MSDN

グループの編集

方法 : デザイナーを使って Windows フォーム ListView コントロールの項目をグループ化する | MSDN
Microsoft Learnから検索