2015年8月31日月曜日

【C#】 仮想ListView で頻繁に RetrieveVirtualItem が呼び出される

C# の Windows フォーム で ListView を仮想モードで動作させると、リストビュー上にマウスカーソルがあるだけで、頻繁に RetrieveVirtualItem が呼び出される、という現象がでます。

初期表示状態では、落ち着いていてもソートやスクロールをした後だと、どうも表示範囲の再表示が完了したことをわかってないみたいで、マウスがじっとしていても何度も何度も呼び出されてしまい、性能の低いPCだと、CPUの使用率が100%になったり、どうかすると真っ白になって固まってしまいます。
(ListViewItem のリストをキャッシュしていても、です)

ネット上にListView のソースらしきものが落ちていましたので、眺めてみましたところ・・・

たとえば、MouseHover の部分を見ると、

        protected override void OnMouseHover(EventArgs e)  {
            ListViewItem item = null;
            if (this.Items.Count > 0) {
                Point pos = Cursor.Position;
                pos = PointToClientInternal(pos);
                item = GetItemAt(pos.X, pos.Y);
            }
 
という記述があり、必要かどうかを判断することなくマウス位置の要素を取り出してます。
ということで、ListView の派生クラスをこさえて、MouseHover を処理しないという暴挙wを働いてみました。

    public class SsListView : System.Windows.Forms.ListView
    {
        public SsListView()
            : base()
        {
        }
        protected override void OnMouseHover(EventArgs e)
        {
             // base.OnMouseHover(e);
        }
   }

デザイナーで読み込む方法を知らないので、フォームのコンストラクタで上記の派生クラスを張り付けた状態で動作させると、マウスが静止しているときには GetItemAt が呼ばれなくなり、CPUの占有がなくなりました。
※この方法では、リスト項目のツールチップが表示されなくなってしまいますので注意。

でも、リストビュー上でマウスを動かし続けると、相変わらず更新され続けてしまいます。
そこで、派生クラスに次のコードを追加してみました。

         const int WM_MOUSEMOVE = 0x0200;
        [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        protected override void WndProc(ref Message msg)
        {
            switch (msg.Msg)
            {
                case WM_MOUSEMOVE:
                    if ((Control.MouseButtons & MouseButtons.Left) != MouseButtons.Left)
                    {
                        return; // 無視させる
                    }
                    break;

            }
            base.WndProc(ref msg);
            return;
        }

ドラッグ処理中じゃなければ、無視するようにしてみたわけです。
これで、マウスをいくら動かしてもCPUを占有しなくなりましたよ。

何らかの弊害があるのかもしれませんが、とりあえずうまくいくようになったみたい・・・

本件について別の情報を入手しましたので追記しておきます。(まだ試してないw)
https://stackoverflow.com/questions/938896/flickering-in-listview-with-ownerdraw-and-virtualmode