17

ユーザーが.NET2.0ListView内のすべてのアイテムを選択すると、ListViewは、選択が変更されたことを示すイベントを発生させるのではなく、すべてのアイテムに対してSelectedIndexChangedイベントを発生させます。

次にユーザーがクリックしてリスト内のアイテムを1つだけ選択すると、ListViewは、選択解除されたすべてのアイテムに対してSelectedIndexChangedイベントを発生させ、次に、新しく選択された単一のアイテムに対してSelectedIndexChangedイベントを発生させます。これは、選択が変更されました。

SelectedIndexChangedイベントハンドラーにコードがある場合、リストに数百/千の項目が含まれ始めると、プログラムはかなり応答しなくなります。

ドウェルタイマーなどを考えました。

しかし、何千もの不必要なListViewを回避するための良い解決策を誰かが持っていますか?SelectedIndexChangeイベント、実際に1つのイベントが実行されるのはいつですか。

4

14 に答える 14

12

イアンからの良い解決策。私はそれを再利用可能なクラスにして、タイマーを適切に破棄するようにしました。また、アプリの応答性を高めるために間隔を短くしました。このコントロールは、ちらつきを減らすためにダブルバッファも使用します。

  public class DoublebufferedListView : System.Windows.Forms.ListView
  {
     private Timer m_changeDelayTimer = null;
     public DoublebufferedListView()
        : base()
     {
        // Set common properties for our listviews
        if (!SystemInformation.TerminalServerSession)
        {
           DoubleBuffered = true;
           SetStyle(ControlStyles.ResizeRedraw, true);
        }
     }

     /// <summary>
     /// Make sure to properly dispose of the timer
     /// </summary>
     /// <param name="disposing"></param>
     protected override void Dispose(bool disposing)
     {
        if (disposing && m_changeDelayTimer != null)
        {
           m_changeDelayTimer.Tick -= ChangeDelayTimerTick;
           m_changeDelayTimer.Dispose();
        }
        base.Dispose(disposing);
     }

     /// <summary>
     /// Hack to avoid lots of unnecessary change events by marshaling with a timer:
     /// http://stackoverflow.com/questions/86793/how-to-avoid-thousands-of-needless-listview-selectedindexchanged-events
     /// </summary>
     /// <param name="e"></param>
     protected override void OnSelectedIndexChanged(EventArgs e)
     {
        if (m_changeDelayTimer == null)
        {
           m_changeDelayTimer = new Timer();
           m_changeDelayTimer.Tick += ChangeDelayTimerTick;
           m_changeDelayTimer.Interval = 40;
        }
        // When a new SelectedIndexChanged event arrives, disable, then enable the
        // timer, effectively resetting it, so that after the last one in a batch
        // arrives, there is at least 40 ms before we react, plenty of time 
        // to wait any other selection events in the same batch.
        m_changeDelayTimer.Enabled = false;
        m_changeDelayTimer.Enabled = true;
     }

     private void ChangeDelayTimerTick(object sender, EventArgs e)
     {
        m_changeDelayTimer.Enabled = false;
        base.OnSelectedIndexChanged(new EventArgs());
     }
  }

これを改善できるかどうか教えてください。

于 2009-06-11T13:11:47.173 に答える
3

これは私が今使用しているドウェルタイマーソリューションです(ドウェルは単に「少し待つ」という意味です)。このコードは、競合状態、およびおそらくnull参照例外の影響を受ける可能性があります。

Timer changeDelayTimer = null;

private void lvResults_SelectedIndexChanged(object sender, EventArgs e)
{
        if (this.changeDelayTimer == null)
        {
            this.changeDelayTimer = new Timer();
            this.changeDelayTimer.Tick += ChangeDelayTimerTick;
            this.changeDelayTimer.Interval = 200; //200ms is what Explorer uses
        }
        this.changeDelayTimer.Enabled = false;
        this.changeDelayTimer.Enabled = true;
}

private void ChangeDelayTimerTick(object sender, EventArgs e)
{
    this.changeDelayTimer.Enabled = false;
    this.changeDelayTimer.Dispose();
    this.changeDelayTimer = null;

    //Add original SelectedIndexChanged event handler code here
    //todo
}
于 2008-09-17T20:21:45.457 に答える
2

私が知っている古い質問ですが、これはまだ問題のようです。

これがタイマーを使用しない私の解決策です。

SelectionChanged イベントを発生させる前に、MouseUp または KeyUp イベントを待機します。プログラムで選択を変更している場合、これは機能せず、イベントは発生しませんが、FinishedChanging イベントまたはイベントをトリガーする何かを簡単に追加できます。

(この質問には関係のないちらつきを止めるためのものもあります)。

public class ListViewNF : ListView
{
    bool SelectedIndexChanging = false;

    public ListViewNF()
    {
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
        this.SetStyle(ControlStyles.EnableNotifyMessage, true);
    }

    protected override void OnNotifyMessage(Message m)
    {
        if(m.Msg != 0x14)
            base.OnNotifyMessage(m);
    }

    protected override void OnSelectedIndexChanged(EventArgs e)
    {
        SelectedIndexChanging = true;
        //base.OnSelectedIndexChanged(e);
    }

    protected override void OnMouseUp(MouseEventArgs e)
    {
        if (SelectedIndexChanging)
        {
            base.OnSelectedIndexChanged(EventArgs.Empty);
            SelectedIndexChanging = false;
        }

        base.OnMouseUp(e);
    }

    protected override void OnKeyUp(KeyEventArgs e)
    {
        if (SelectedIndexChanging)
        {
            base.OnSelectedIndexChanged(EventArgs.Empty);
            SelectedIndexChanging = false;
        }

        base.OnKeyUp(e);
    }
}
于 2014-01-15T11:25:18.840 に答える
1

タイマーは、全体的な最適なソリューションです。

Jens の提案の問題点は、リストに多数の項目 (数千以上) が選択されると、選択項目のリストを取得するのに時間がかかり始めることです。

SelectedIndexChanged イベントが発生するたびにタイマー オブジェクトを作成する代わりに、デザイナーを使用して永続的なタイマー オブジェクトをフォームに配置し、クラス内のブール変数をチェックして、更新関数を呼び出す必要があるかどうかを確認する方が簡単です。

例えば:

bool timer_event_should_call_update_controls = false;

private void lvwMyListView_SelectedIndexChanged(object sender, EventArgs e) {

  timer_event_should_call_update_controls = true;
}

private void UpdateControlsTimer_Tick(object sender, EventArgs e) {

  if (timer_event_should_call_update_controls) {
    timer_event_should_call_update_controls = false;

    update_controls();
  }
}

これは、ステータス バーを更新して "Y のうち X が選択されました" と表示するなど、単に表示目的で情報を使用している場合には問題なく機能します。

于 2009-07-07T02:06:32.610 に答える
1

Windowsフォーム/Webフォーム/モバイルフォームのOnLoadイベントにフラグが働きます。複数選択ではなく単一選択リストビューでは、次のコードは実装が簡単で、イベントの複数回の発生を防ぎます。

ListView が最初の項目の選択を解除すると、2 番目の項目が必要になり、コレクションには 1 つの項目のみが含まれるようになります。

以下は同じものをモバイル アプリケーションで使用したため、コンパクト フレームワークを使用しているため、一部のコレクション名が異なる場合がありますが、同じ原則が適用されます。

注: 最初の項目を選択するように設定したリストビューの OnLoad と populate を確認してください。

// ################ CODE STARTS HERE ################
//Flag  to create at the form level
System.Boolean lsvLoadFlag = true;

//Make sure to set the flag to true at the begin of the form load and after
private void frmMain_Load(object sender, EventArgs e)
{
    //Prevent the listview from firing crazy in a single click NOT multislect environment
    lsvLoadFlag = true;

    //DO SOME CODE....

    //Enable the listview to process events
    lsvLoadFlag = false;
}

//Populate First then this line of code
lsvMain.Items[0].Selected = true;

//SelectedIndexChanged Event
 private void lsvMain_SelectedIndexChanged(object sender, EventArgs e)
{
    ListViewItem lvi = null;

    if (!lsvLoadFlag)
    {
        if (this.lsvMain.SelectedIndices != null)
        {
            if (this.lsvMain.SelectedIndices.Count == 1)
            {
                lvi = this.lsvMain.Items[this.lsvMain.SelectedIndices[0]];
            }
        }
    }
}
################ CODE END HERE    ################

理想的には、このコードを UserControl に配置して、1 つの選択 ListView で簡単に再利用および配布できるようにする必要があります。このコードは、複数選択ではあまり使用されません。イベントはその動作に対して適切に機能するためです。

それが役立つことを願っています。

敬具、

アンソニー・N・アーウィン http://www.manatix.com

于 2010-09-06T11:53:58.740 に答える
1

async&を使用できますawait

private bool waitForUpdateControls = false;

private async void listView_SelectedIndexChanged(object sender, EventArgs e)
{
    // To avoid thousands of needless ListView.SelectedIndexChanged events.

    if (waitForUpdateControls)
    {
        return;
    }

    waitForUpdateControls = true;

    await Task.Delay(100);

    waitForUpdateControls = false;

    UpdateControls();

    return;
}
于 2017-02-21T18:44:54.853 に答える
0

メイロン >>>

目的は、数百のアイテムを超えるリストを操作することではありませんでしたが... 10.000 アイテムで全体的なユーザー エクスペリエンスをテストし、一度に 1000-5000 アイテムを選択しました (両方の選択されたアイテムで 1000-3000 アイテムの変更)および選択解除)...

計算の全体的な所要時間は 0.1 秒を超えることはありませんでした。最高の測定値のいくつかは 0.04 秒でした。これほど多くの項目で完全に許容できることがわかりました。

そして 10.000 アイテムでは、リストを初期化するだけで 10 秒以上かかるので、この時点で私は、Joe Chung の仮想化が指摘しているように、他のことが関係していると思っていたでしょう。

そうは言っても、コードが選択の違いを計算する方法において最適なソリューションではないことは明らかです。必要に応じて、これはさまざまな方法で大幅に改善できます。コードではなく、概念の理解に焦点を当てました。パフォーマンスより。

ただし、パフォーマンスの低下を経験している場合は、次のいくつかに非常に興味があります。

  • リストにはいくつの項目がありますか?
  • 一度にいくつの要素を選択/選択解除しますか?
  • イベントが発生するまでにおよそどのくらいかかりますか?
  • ハードウェア プラットフォーム?
  • 詳しくはこちら 使用例は?
  • 他に考えられる関連情報はありますか?

そうでなければ、ソリューションの改善を助けることは容易ではありません。

于 2009-07-07T08:27:50.520 に答える
0

ListViewとすべての古いコントロールを残します。

DataGridViewあなたの友達を作ってください、そうすればすべてがうまくいくでしょう:)

于 2009-07-07T08:32:09.060 に答える
0

数百または数千のアイテムがある場合は、リスト ビューを仮想化することをお勧めします。

于 2009-07-07T02:14:11.993 に答える
0

もっと良い解決策があるかもしれません。

私の状況:

  • 単一選択リスト ビュー (複数選択ではなく)
  • 以前に選択したアイテムの選択解除のために発生したイベントの処理を避けたいです。

私の解決策:

  • ユーザーが MouseDown でクリックした項目を記録する
  • このアイテムが null ではなく、SelectedIndexes.Count == 0 の場合、SelectedIndexChanged イベントを無視します。

コード:

ListViewItem ItemOnMouseDown = null;
private void lvTransactions_MouseDown(object sender, MouseEventArgs e)
{
    ItemOnMouseDown = lvTransactions.GetItemAt(e.X, e.Y);
}
private void lvTransactions_SelectedIndexChanged(object sender, EventArgs e)
{
    if (ItemOnMouseDown != null && lvTransactions.SelectedIndices.Count == 0)
        return;

    SelectedIndexDidReallyChange();

}
于 2011-08-10T03:49:02.787 に答える
0

おそらく、これはタイマーを使用せずに必要なことを達成するのに役立つかもしれません:

http://www.dotjem.com/archive/2009/06/19/20.aspx

私はタイマーなどのユーザーが好きではありません。私も投稿で述べているように...

それが役に立てば幸い...

言い忘れましたが、それは .NET 3.5 です。linq のいくつかの機能を使用して、oO と呼べる場合は「選択変更の評価」を実行しています。

とにかく、古いバージョンを使用している場合、この評価はもう少しコードで行う必要があります... >.<...

于 2009-06-19T16:51:41.050 に答える
0

ポストバックをボタンに結び付けて、ユーザーが変更を送信し、イベント ハンドラーをアンフックできるようにします。

于 2008-09-17T19:47:53.310 に答える
0

昨日、まさにこの問題に取り組もうとしていたところです。「滞留」タイマーの意味が正確にはわかりませんが、すべての変更が完了するまで待機する独自のバージョンを実装してみました。残念ながら、これを行う唯一の方法は別のスレッドで行うことでした。別のスレッドを作成すると、そのスレッドで UI 要素にアクセスできないことがわかりました。.NET は、要素が作成されたスレッドでのみ UI 要素にアクセスできることを示す例外をスローします! そのため、SelectedIndexChanged への応答を最適化し、耐えられるところまで十分に高速にする方法を見つけましたが、スケーラブルなソリューションではありません。誰かがこの問題に 1 つのスレッドで取り組む賢いアイデアを持っていることを期待しましょう。

于 2008-09-17T19:48:10.187 に答える
0

Raymond Chenのブログ記事では、変更イベントが 1 つだけではなく、何千もある理由を (おそらく) 説明しています。

完全に適切な LVN_ITEMCHANGED 通知が既にあるのに、なぜ LVN_ODSTATECHANGED 通知があるのですか?

...通知は、指定した範囲内のすべてのアイテムの状態が変化したことを示しています
。範囲内のすべてのアイテムに対してLVN_ODSTATECHANGED個別に送信することの省略形です。500,000 項目の所有者データ リスト ビューがあり、誰かがすべて選択を行った場合、50万の個々の小さな通知ではなく、 単一の 通知を受け取ることを嬉しく思います 。LVN_ITEMCHANGED[iFrom..iTo]LVN_ODSTATECHANGEDiFrom=0iTo=499999LVN_ITEMCHANGED

.NET リスト ビューがListview Common Control のラッパーであるという保証はないため、おそらくその理由を説明すると思います。これは、いつでも自由に変更できる実装の詳細です (ただし、ほぼ確実に変更されることはありません)。

示唆された解決策は、仮想モードで .NET リストビューを使用することです。これにより、コントロールの使用が桁違いに難しくなります。

于 2010-10-28T18:10:59.370 に答える