ListView.Details
型 | プロパティ | 内容 | 既定値 |
---|---|---|---|
bool | VirtualMode | trueならば、独自にデータを管理する仮想モード (virtual mode) となる | false |
ListView.ListViewItemCollection | Items | すべての項目を格納する、ListViewItemのコレクション | |
ListView.ColumnHeaderCollection | Columns | すべての列ヘッダー (ColumnHeader) のコレクション | |
ListView.SelectedIndexCollection | SelectedIndices | 選択されている項目のインデックスが返される | |
ListView.SelectedListViewItemCollection | SelectedItems | 選択されている項目が返され、それがなければ空のコレクションが返される | |
ListViewItem | FocusedItem | 現在フォーカスがある項目。それがなければnull
たいてい最後に選択された項目がそれに該当するが、フォーカスがある項目が選択されているとは限らない。このプロパティは、最後にクリックされた項目を決定するのに用いる IsHandleCreatedがfalseのときに設定すると、無視される FocusedItem - ListView.cs |
|
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 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()で追加できます。
項目を追加しても何も表示されないならば、ViewとColumnsプロパティの設定を確認します。
メソッド | 削除対象 |
---|---|
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からその要素を削除します。
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つは異なるオブジェクトですが、同一の内容となります。
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
選択されている項目のコレクションを得られます。一方で選択されている項目を変更するには、ListViewItem.Selectedに設定します。
このプロパティから返されるListView.SelectedListViewItemCollectionクラスのコレクションを列挙するときは、LVM_GETNEXTITEMメッセージで得られたインデックスからひとつずつ項目が取得された上で返されます。よって個々の項目が不要ならば、SelectedIndicesの方が効率的です。 SelectedItems - ListView.cs SelectedItemArray - ListView.cs
すべての選択はSelectedItems.Clear()とすることで解除でき、何も選択されていない状態にできます。ただしこのメソッドはListViewItem.Selectedを1つずつfalseに設定するため、SelectedIndices.Clear()でLVM_SETITEMSTATEメッセージを用いてすべての項目の状態を解除する方が効率的です。 SelectedItems.Clear - ListView.cs SelectedIndices.Clear - ListView.cs
列挙子 | |
---|---|
View.Details※1 | |
View.SmallIcon | |
View.LargeIcon | |
View.List | |
View.Tile※1 |
※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 | ○ |
画像の表示サイズは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で指定します。
サブ項目も含めた項目を表示するのに必要な数だけ追加します。列が不足する場合にはその項目の内容が表示されず、超過する場合には空欄となります。
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.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を機能させるには、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"という名前の既定のグループに割り当てられます。
グループを非表示にしたり折りたたんだりする機能は提供されていないため、その必要があるときにはグループに割り当てた項目をすべて削除するなどします。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
コンストラクタのListViewGroup(string header)
とListViewGroup(string key, string headerText)
の、headerとheaderTextは同じであり、Headerプロパティに設定されます。またkeyはNameプロパティの値となります。ListViewGroup - ListViewGroup
型 | プロパティ | 内容 |
---|---|---|
string | Header | グループのヘッダー テキスト |
string | Name | グループの名前 |
ListView | ListView | グループを格納しているListView |
ListView.ListViewItemCollection | Items | グループ内の項目を格納するコレクション |
グループはListViewGroupのインスタンスごとに分類されるため、 HeaderやNameプロパティの値が重複しても構いません。
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イベントに応じなければ何も起きません。
GroupsはIListを継承したコレクションであるため、それの操作と同様の方法で並べ替えられます。
ListViewGroup group = listView.Groups[1]; // インデックス1の項目を取得 listView.Groups.Remove(group); // それをコレクションから削除 listView.Groups.Insert(0, group); // それをインデックス0の位置に挿入
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()を明示的に呼び出す
}
項目にアイコンを表示するときの、ImageListを設定します。この中のどの画像を個々の項目に用いるかは、ListViewItem.ImageIndexで指定します。ListView.LargeImageList プロパティ (System.Windows.Forms)
Visual StudioのGUIからこれを設定するには、ツールボックスからImageListのコンポーネントを追加し、それをプロパティに設定します。
項目をマウスでアクティブにするための条件を設定します。キーボードにはこの設定は作用せず、つねにEnterです。
メンバ | アクティブにする方法 | マウスオーバー時の動作 | ラベルの編集 |
---|---|---|---|
OneClick | シングルクリック | カーソル形状が変わり、項目のテキストの色が変わる | 不可 |
Standard [既定] | ダブルクリック | ない | 可能 |
TwoClick | 任意の間隔のダブルクリック | 項目のテキストの色が変わる | 不可 |
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
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
ActivationがStandardのときにこのプロパティをtrueにすると、ユーザーが項目のテキストを編集できるようになります。ただしサブ項目は編集できません。ListView.LabelEdit プロパティ (System.Windows.Forms) | MSDN
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) | 指定位置の項目が可視となるように、コントロールの内容をスクロールする |
指定のテキストから始まる項目を、項目やサブ項目から見つけられます。
public ListViewItem FindItemWithText(
string text,
bool includeSubItemsInSearch,
int startIndex // 検索を開始する項目のインデックス
)
ListView.FindItemWithText メソッド (String, Boolean, Int32) (System.Windows.Forms) | MSDN
これをキーで見つけるには、ListViewItem.Nameプロパティで名前を指定しておき、ListView.ListViewItemCollection.Find()で検索します。
指定の座標位置にある項目の情報を得られます。
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となります。
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つです。
指定位置にある項目を得られます。
public System.Windows.Forms.ListViewItem GetItemAt (int x, int y);ListView.GetItemAt(Int32, Int32) Method (System.Windows.Forms) | Microsoft Learn
情報はHitTest()と同様にLVM_HITTESTメッセージで取得するため、指定位置にグループのヘッダーテキストがあるとnullが返されます。
指定項目の指定部分に外接する四角形を取得できます。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プロパティから取得します。
ListViewItemが既知ならば、ListViewItem.EnsureVisible()を呼んでも同じです。EnsureVisible - ListViewItem.cs
指定の項目がすでに表示されているならば、スクロールされません。これを指定量だけ確実にスクロールさせるには、LVM_SCROLLメッセージを送ります。
public void BeginUpdate ();ListView.BeginUpdate メソッド (System.Windows.Forms) | Microsoft Learn
WM_SETREDRAWメッセージにより描画が停止させられます。また内部ではupdateCounterが加算され、これが0より大きい間は項目の追加時に表示が更新されません。 BeginUpdate - ListView.cs InsertItems - ListView.cs
複数列で表示する形式でキーで操作するとき、View.Listではインデックス順でフォーカスを移動させられますが、それ以外のView.SmallIconやView.Tileでは端の列から次のインデックスへ移動しません。これをキーで移動するようにするには、ProcessCmdKey()でフォーカスを設定します。
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData == Keys.Right && FocusedItem != null) { int nextIndex = FocusedItem.Index + 1; if (nextIndex < Items.Count) { ListViewItem item = 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では発生しない |
アクティブになる条件は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 = SelectedIndices.Count; if (count != 0) { const int WAIT_FOR_SELECTION_TO_BE_FIXED = 100; await Task.Delay(WAIT_FOR_SELECTION_TO_BE_FIXED).ContinueWith((_task) => { if (count == SelectedIndices.Count) // 変更が確定した { base.OnItemActivate(e); } }, TaskScheduler.FromCurrentSynchronizationContext()).ConfigureAwait(false); } } 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); }
このイベントはSelectedIndicesが変更されたときに発生するため、そのたびに選択されている項目の数が変化します。たとえば3つの項目が選択されている状態で、それ以外の項目を1つ選択すると4回発生し、そのたびにListView.SelectedIndices.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)
{
// 選択が確定された
}
}
このイベントは、個々の項目の選択状態が変更されるたびに発生します。よって複数の項目が選択または選択解除された場合には、その項目の数だけイベントが発生します。
変更が選択か解除かは、引数のListViewItemSelectionChangedEventArgs.IsSelectedで判別できます。
MouseClickは、項目がクリックに応答する範囲内でしか発生しません。一方でMouseDownは、その範囲外とグループのヘッダーテキストでも発生します。ただし両方とも、列ヘッダへのクリックでは発生しません。
MouseDownはグループのヘッダーテキストでも発生するため、その位置に対してLVM_HITTESTメッセージを送ると、クリックされたグループを特定できます。ただしグループを後から追加したときはLVM_HITTESTの戻り値はグループのインデックスと一致しなくなるため、ListViewGroup.IDから特定するようにします。 c# - ListView Group Header Click - How to add a context menu to ListView Group Headers? - Stack Overflow LVM_HITTEST メッセージ (Commctrl.h) - Win32 apps | Microsoft Learn
MouseClick同様に、項目がクリックに応答する範囲内でしか発生しません。よってそれ以外にはWndProc()をオーバーライドして、WM_LBUTTONDBLCLK (0x0203) のときに処理します。
項目をマウスボタンで押下したときは、MouseUpが発生した時点で選択された状態となっています。一方でグループのヘッダーテキストでは、マウスボタンを離すタイミングによっては対象の項目が選択される前の状態であることがあります。
列ヘッダのクリックはこの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 != Handle) // ユーザーが右クリックしたウィンドウへのハンドルと、ListViewのハンドルが異なる { // ヘッダが右クリックされた } else { // ヘッダ以外が右クリックされた } int param = m.LParam.ToInt32(); int x = param & 0x0000FFFF; int y = param >> 16; Point clickLocation = PointToClient(new Point(x, y)); } }c# - ListView ContextMenuStrip for column headers - Stack Overflow WM_CONTEXTMENU メッセージ (Winuser.h) - Win32 apps | Microsoft Learn
ハンドラの引数のColumnWidthChangingEventArgs.Cancelをtrueとすることで列の幅が変更されていないように表示されますが、ドラッグが終了すると、そのときに指定された幅が適用されてしまいます。よって指定された幅を無効とするには、ColumnWidthChangedにも応答して適正な値に修正します。
このイベントが発生しないときは、OSを再起動してみます。
項目の描画に、オーナー描画 (owner drawing / オーナードロー / カスタム描画) が必要なときに発生します。ListView.DrawItem Event (System.Windows.Forms) | Microsoft Learn
OwnerDrawプロパティをtrueとしたならば、DrawItemに応答し描画せねばなりません。さもなくば項目が何も表示されなくなります。
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
名前からはサブ項目の描画時のみ発生しそうですが、インデックス0の列の描画でも発生するため、項目も対象となります。ListView.DrawSubItem Event (System.Windows.Forms) | Microsoft Learn
DrawItemのハンドラでDrawDefaultをtrueにすると、このDrawSubItemは発生しません。また描画すべきサブ項目が存在しない項目の列でも、発生しません。
このメソッドは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, Font, bounds, 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