LoginSignup
0
2

More than 3 years have passed since last update.

WinFormsのListView.SelectedIndicesが遅い問題を回避する

Posted at

仮想モードにしたListViewでも、SelectedIndicesプロパティ(ListView.SelectedIndexCollectionクラス)で非常に時間がかかるケースがあります。
(この記事では面倒なのでどれくらい時間がかかるかは述べません)

遅くならないケース

  • SelectedIndices.Count
    内部ではLVM_GETSELECTEDCOUNTを使って実装されているので高速に取得できます。
  • SelectedIndices[0]
    1件目であれば、列挙を打ち切るため遅くはなりません。

全選択/全選択解除を早くする

以下のように、ループでSelectedIndices.Add/Removeでしていくと非常に時間がかかってしまいます。

NG
for (var i = 0; i < listView1.Items.Count; i++)
{
    listView1.SelectedIndices.Add(i);
}

ListBoxの全項目を高速に選択すると同様に、P/Invokeで全選択するようにします。

こんなのを定義して、

const int LVM_FIRST = 0x1000;
const int LVM_SETITEMSTATE = LVM_FIRST + 43;

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct LVITEM
{
    public int mask;
    public int iItem;
    public int iSubItem;
    public int state;
    public int stateMask;
    [MarshalAs(UnmanagedType.LPTStr)]
    public string pszText;
    public int cchTextMax;
    public int iImage;
    public IntPtr lParam;
    public int iIndent;
    public int iGroupId;
    public int cColumns;
    public IntPtr puColumns;
};
private const int LVIS_SELECTED = 2;

public static void SelectAll(ListView control, bool select)
{
    var lvItem = new LVITEM
    {
        stateMask = LVIS_SELECTED,
        state = select ? LVIS_SELECTED : 0
    };
    SendMessage(new HandleRef(control, control.Handle), LVM_SETITEMSTATE, new UIntPtr(unchecked((uint)-1)), ref lvItem);
}

[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SendMessage(HandleRef hWnd, int msg, UIntPtr wParam, ref LVITEM lvi);

こう使います。

    // 全選択
    SelectAll(listview1, true);

    // 選択解除
    SelectAll(listview1, false);

選択行の取得を早くする

SelectedIndicesに対して列挙すると、内部では1件目から全部舐めてリストを作ることになるので、非常に遅くなります。
(foreachで回さなくても、CopyToメソッドなども含む)
大きなインデックスでインデックスアクセスしても同様にその位置まで1件進めるのに時間がかかります。

NG
foreach (int i in listView1.SelectedIndices)
{
    // iを使用した処理
}

そのため、VirtualItemsSelectionRangeChangedItemSelectionChangedイベントで選択行を保持しておき、選択行が必要なときはそれを参照するようにします。

private bool[] _selected = new bool[/*listView1.VirtualListSize分のサイズ*/];
private void listView1_VirtualItemsSelectionRangeChanged(object sender, ListViewVirtualItemsSelectionRangeChangedEventArgs e)
{
    for (var i = e.StartIndex;  i <= e.EndIndex; i++)
    {
        _selected[i] = e.IsSelected;
    }
}
private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
{
    _selected[e.ItemIndex] = e.IsSelected;
}

他の方法として、

  • C#を諦めてC++/CLIで同等の処理を実装する
  • 別途ネイティブDLLを作成してそれをP/Invokeで呼び出す

などもあります。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2