2008-09-17 16 views
16

Nếu người dùng chọn tất cả các mục trong danh sách .NET 2.0, ListView sẽ kích hoạt sự kiện SelectedIndexChanged cho mọi mục, thay vì kích hoạt sự kiện để cho biết rằng lựa chọn đã thay đổi.Làm thế nào để tránh hàng ngàn sự kiện ListView.SelectedIndexChanged không cần thiết?

Nếu người dùng sau đó nhấp chuột để chọn chỉ một mục trong danh sách, ListView sẽ cháy một SelectedIndexChanged sự kiện cho mỗi mục mà là nhận được không được chọn, và sau đó một SelectedIndexChanged sự kiện cho đơn hàng mới được chọn , thay vì kích hoạt sự kiện để cho biết lựa chọn đã thay đổi.

Nếu bạn có mã trong trình xử lý sự kiện SelectedIndexChanged, chương trình sẽ trở nên khá không phản hồi khi bạn bắt đầu có vài trăm nghìn mục trong danh sách.

Tôi đã nghĩ về dừng giờ vv

Nhưng không ai có một giải pháp tốt để tránh hàng ngàn ListView không cần thiết. SelectedIndexChange sự kiện, khi thực sự một sự kiện sẽ làm gì?

Trả lời

11

Giải pháp tốt từ Ian. Tôi lấy nó và biến nó thành một lớp có thể tái sử dụng, đảm bảo vứt bỏ bộ hẹn giờ đúng cách. Tôi cũng giảm khoảng thời gian để có được ứng dụng đáp ứng nhanh hơn. Điều khiển này cũng tăng gấp đôi để giảm nhấp nháy.

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()); 
    } 
    } 

Hãy cho tôi biết nếu điều này có thể được cải thiện.

+0

+1 cho chỉ đệm đôi nếu không có trong một phiên đầu cuối/RDP –

+0

tôi sẽ chấp nhận câu trả lời này, mà không kiểm tra mã. i ** hy vọng ** không có sự cố nào xảy ra. –

+0

Nếu có, hãy cho tôi biết. :) –

0

Tôi sẽ thử buộc việc đăng lại một nút để cho phép người dùng gửi các thay đổi của họ và mở trình xử lý sự kiện.

+0

Winforms.ListView –

0

Tôi vừa cố giải quyết vấn đề này ngày hôm qua. Tôi không biết chính xác những gì bạn có ý nghĩa bởi "ngự" giờ, nhưng tôi đã cố gắng thực hiện phiên bản của riêng tôi chờ đợi cho đến khi tất cả các thay đổi được thực hiện. Thật không may là cách duy nhất tôi có thể nghĩ đến để làm điều này là trong một chủ đề riêng biệt và nó chỉ ra rằng khi bạn tạo một chủ đề riêng biệt, các yếu tố giao diện người dùng của bạn là không thể tiếp cận trong chủ đề đó. .NET ném một ngoại lệ cho biết rằng các phần tử giao diện người dùng chỉ có thể được truy cập trong chuỗi nơi các phần tử được tạo! Vì vậy, tôi tìm thấy một cách để tối ưu hóa phản ứng của tôi cho SelectedIndexChanged và làm cho nó đủ nhanh để nó được chấp nhận - nó không phải là một giải pháp khả năng mở rộng mặc dù. Cho phép hy vọng ai đó có một ý tưởng thông minh để giải quyết vấn đề này trong một chủ đề duy nhất.

+0

tôi tạo ra một câu trả lời cho thấy khái niệm "sống". Bạn bắt đầu hẹn giờ trong OnChange và sau đó 200ms tất cả các selet sẽ được thực hiện và sau đó bạn có thể kích hoạt sự kiện Thay đổi ** thực **. –

2

Đây là giải pháp hẹn giờ dừng tôi đang sử dụng bây giờ (chỉ có nghĩa là "chờ một chút"). Mã này có thể bị một điều kiện chủng tộc, và có lẽ là một ngoại lệ tham chiếu 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 
} 
+0

Cần lưu ý rằng giải pháp 'sống' này không phải là câu trả lời. Đó là workaround hack tôi thực hiện cho đến khi tôi có thể nhận được một câu trả lời thực sự. –

+0

Sự kiện của lớp Timer chạy trong chuỗi giao diện người dùng, do đó mã sẽ hoạt động như mong đợi. – Thanatos

+0

Điều đó không có nghĩa là mã sẽ dừng bộ hẹn giờ đúng cách khi biểu mẫu đóng hoặc không cố khởi động bộ hẹn giờ khác khi bộ định thời đầu tiên đi hoặc bộ hẹn giờ không thể kích hoạt sau khi biểu mẫu đã được xử lý hoặc đó không phải là null trước khi nó được giới thiệu. "Chỉ vì nó hoạt động không có nghĩa là nó đúng." –

0

Có lẽ điều này có thể giúp bạn thực hiện những gì bạn cần mà không sử dụng tính giờ:

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

I Do not thích người sử dụng giờ vv. Vì tôi cũng nêu rõ trong bài đăng ...

Hy vọng điều này sẽ giúp ...

Ohh tôi quên nói, đó là .NET 3.5, và tôi đang sử dụng một số tính năng trong LINQ để hoàn thành "Lựa chọn thay đổi đánh giá" nếu bạn có thể gọi nó là oO ..

Dù sao, nếu bạn đang ở trên một phiên bản cũ, đánh giá này phải được thực hiện với một nhiều mã hơn ...>. < ...

+0

Bạn nên đưa ra một dòng về giải pháp của bạn. Nhìn vào mã tôi không thực sự biết những gì bạn đang cố gắng để đạt được. Bạn dường như đang ném một sự kiện khi lựa chọn thay đổi ... đó là vấn đề tôi đang gặp phải. –

1

Bộ hẹn giờ là giải pháp tổng thể tốt nhất.

Một vấn đề với gợi ý của Jens là khi danh sách có nhiều mục được chọn (hàng nghìn trở lên), việc nhận danh sách các mục đã chọn bắt đầu mất nhiều thời gian. Thay vì tạo một đối tượng hẹn giờ mỗi khi một sự kiện SelectedIndexChanged xảy ra, nó đơn giản hơn để đặt một biến vĩnh viễn trên biểu mẫu với trình thiết kế và kiểm tra biến boolean trong lớp để xem có nên gọi nó hay không. chức năng cập nhật.

Ví dụ:

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(); 
    } 
} 

này hoạt động tốt nếu bạn đang sử dụng các thông tin chỉ vì mục đích hiển thị, chẳng hạn như cập nhật một thanh trạng thái để nói "X ra khỏi Y được chọn".

0

Tôi khuyên bạn nên ảo hóa chế độ xem danh sách nếu nó có hàng trăm hoặc hàng nghìn mục.

+1

Thực hiện xem danh sách ảo không cho phép bạn chọn các mục? –

0

Maylon >>>

Mục đích là không bao giờ làm việc với danh sách trên một vài mặt hàng trăm, Nhưng ... Tôi đã thử nghiệm kinh nghiệm người dùng chung với 10.000 mặt hàng, và lựa chọn của 1000-5000 mục ở một lần (và thay đổi 1000-3000 mục trong cả Được chọn và Bỏ chọn) ...

Tổng thời gian tính không bao giờ vượt quá 0,1 giây, một số phép đo cao nhất là 0,04 giây, tôi thấy hoàn toàn chấp nhận được Nhiều mặt hàng.

Và tại 10.000 mục, chỉ cần khởi tạo danh sách mất hơn 10 giây, do đó, tại thời điểm này tôi đã nghĩ rằng những thứ khác đã đến để chơi, như ảo hóa như Joe Chung chỉ ra.

Điều đó nói rõ ràng rằng mã không phải là giải pháp tối ưu trong cách tính toán sự khác biệt trong lựa chọn, nếu cần điều này có thể được cải thiện rất nhiều và theo nhiều cách khác nhau, tôi tập trung vào sự hiểu biết về khái niệm với mã thay vì hiệu suất.

Tuy nhiên, nếu bạn trải qua thực thi giảm sút Tôi rất quan tâm đến một số điều sau đây:

  • Có bao nhiêu mặt hàng trong danh sách?
  • Có bao nhiêu yếu tố được chọn/bỏ chọn tại một thời điểm?
  • Mất bao lâu để sự kiện tăng?
  • Nền tảng phần cứng?
  • Thông tin thêm về trường hợp sử dụng?
  • Thông tin liên quan khác mà bạn có thể nghĩ đến?

Nếu không, sẽ không dễ giúp cải thiện giải pháp.

+0

Có 10.000 mục trong danh sách và nhấn phím tắt "Chọn tất cả" của bạn. Sau đó xóa lựa chọn. –

+0

Khái niệm này rất đơn giản, bạn phản ứng với đầu vào của người dùng thay vì thay đổi thuộc tính trên các thành phần hoặc giống nhau. Bạn cần sử dụng các sự kiện mới, vì cũ sẽ hoạt động như thường lệ, các sự kiện mới sẽ kiểm tra xem các thay đổi lựa chọn đã được thực hiện cho chế độ xem danh sách khi 1 trong 2 điều xảy ra. 1. Phím Chuột được nhả ra. 2. Phím Bàn phím được nhả. Bằng cách đó, Sự kiện chỉ kích hoạt tương tác theo tương tác của người dùng, thay vì các sự kiện trên mỗi thay đổi phần tử. Và nếu có, nó sẽ tăng sự kiện "ListSelectionChanged". Phím tắt "Chọn tất cả" không hoạt động theo mặc định với một ListView, do đó phải là thứ bạn đã thêm? – Jens

0

Rời khỏi ListView và tất cả các điều khiển cũ.

Hãy DataGridView bạn bè của bạn, và tất cả sẽ tốt :)

+1

Miễn là tôi có thể sử dụng nó mà không cần databinding –

1

Một lá cờ làm việc cho sự kiện onload của các cửa sổ dạng/mẫu web hình thức/mobile. Trong một Chế độ xem danh sách chọn, không chọn nhiều, mã sau đây rất đơn giản để triển khai và ngăn nhiều lần kích hoạt sự kiện.

Khi ListView bỏ chọn mục đầu tiên, mục thứ hai đó là thứ bạn cần và bộ sưu tập chỉ nên chứa một mục.

Điều tương tự dưới đây được sử dụng trong ứng dụng dành cho thiết bị di động, do đó một số tên bộ sưu tập có thể khác nhau vì sử dụng khung nhỏ gọn.

Lưu ý: Đảm bảo OnLoad và điền vào chế độ xem danh sách bạn đặt mục đầu tiên sẽ được chọn.

// ################ 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 ################ 

Lý tưởng nhất, mã này nên được đưa vào UserControl để dễ dàng sử dụng lại và phân phối trong một ListView chọn duy nhất. Mã này sẽ không được sử dụng nhiều trong một đa lựa chọn, vì sự kiện hoạt động như nó cần cho hành vi đó.

Tôi hy vọng điều đó sẽ hữu ích.

Trân trọng!

Anthony N. Urwin http://www.manatix.com

0

Raymond Chen has a blog post that (probably) explains why there are thousands of change events, chứ không phải chỉ là một:

Why is there an LVN_ODSTATECHANGED notification when there's already a perfectly good LVN_ITEMCHANGED notification?

...
Các LVN_ODSTATECHANGED thông báo cho bạn biết ở trạng thái của tất cả các mục trong phạm vi được chỉ định đã thay đổi. Đó là một cách viết tắt để gửi từng cá nhân LVN_ITEMCHANGED cho tất cả các mục trong phạm vi [iFrom..iTo]. Nếu bạn có một cái nhìn danh sách ownerdata với 500.000 mục và ai đó làm một chọn-tất cả, bạn sẽ được vui mừng mà bạn nhận được một đơn thông báo LVN_ODSTATECHANGED với iFrom=0iTo=499999 thay vì nửa triệu cá nhân ít LVN_ITEMCHANGED thông báo.

tôi nói lẽ giải thích lý do tại sao, vì không có gì bảo đảm rằng giao diện danh sách NET là một wrapper quanh listview điều khiển chung - đó là một chi tiết thực hiện đó là miễn phí để thay đổi bất cứ lúc nào (mặc dù gần như chắc chắn không bao giờ sẽ).

Giải pháp được gợi ý là sử dụng danh sách .NET listview ở chế độ ảo, giúp việc kiểm soát trở nên khó khăn hơn khi sử dụng.

0

Tôi có thể có giải pháp tốt hơn.

tình hình của tôi:

  • Độc chọn xem danh sách (chứ không phải chọn nhiều)
  • Tôi muốn tránh chế biến sự kiện khi nó cháy cho deselection của mục đã chọn trước đó.

Giải pháp của tôi:

  • Ghi lại những gì mục người dùng nhấp vào MouseDown
  • Bỏ qua sự kiện SelectedIndexChanged nếu mặt hàng này không phải là vô SelectedIndexes.Count == 0

Mã :

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(); 

} 
1

Old que stion tôi biết, nhưng điều này vẫn có vẻ là một vấn đề.

Đây là giải pháp của tôi không sử dụng bộ hẹn giờ.

Nó chờ sự kiện MouseUp hoặc KeyUp trước khi kích hoạt sự kiện SelectionChanged. Nếu bạn đang thay đổi lựa chọn theo chương trình, thì điều này sẽ không hoạt động, sự kiện sẽ không kích hoạt, nhưng bạn có thể dễ dàng thêm sự kiện FinishedChanging hoặc một thứ gì đó để kích hoạt sự kiện.

(Nó cũng có một số nội dung để dừng nhấp nháy không liên quan đến câu hỏi này).

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); 
    } 
} 
+0

Hoạt động như một sự quyến rũ. Cảm ơn. Đây sẽ là câu trả lời được chấp nhận vì nó không phụ thuộc vào giờ. –

1

Bạn có thể sử dụng 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; 
} 
Các vấn đề liên quan