2009-04-16 22 views
1

我正在寫一個簡單的自定義的所有者繪製的ListBox控件,並將我的頭靠在牆上試圖弄清楚如何實現看起來像直髮功能的東西。我的自定義ListBox應該類似於Windows XP中的「添加/刪除程序」列表。也就是說,它像往常一樣顯示項目列表,但是當用戶選擇列表中的項目時,項目文本旁邊應該會出現可點擊的按鈕。就我而言,我試圖在我的ListBox中的每個項目旁邊顯示一個「導入」按鈕。在WinForms列表框的每個項目中繪製「子」控件(如按鈕)的最佳方式是什麼?

爲了保持自定義ListBox有點封裝,我試圖通過重寫ListBox.OnDrawItem方法(即按鈕在概念上列表框項目的一部分)來顯示按鈕,但我無法讓它工作完全正確。

我應該注意到,我正在嘗試使用整個ListBox的單個按鈕。當用戶選擇列表中的項目時,OnDrawItem方法只是重新定位此單個按鈕,以使其顯示在所選項目旁邊。我有99%的工作:現在的問題是,當滾動ListBox並且選擇的項目離開屏幕時,按鈕仍然被拖動到其之前的位置,因此它將繪製在錯誤的項目之上。我猜這是因爲Windows不會嘗試重新繪製選中的項目,因爲它不在屏幕上,因此重定位代碼不會被調用。

這裏是什麼,我現在所擁有的精簡版:

public partial class EventListBox : ListBox 
{ 
    private Button _importButton; 

    public EventListBox() 
    { 
     InitializeComponent(); 
     this.DrawMode = DrawMode.OwnerDrawVariable; 

    // set up the button that will appear next to the currently-selected item 
     _importButton = new Button(); 
     _importButton.Text = "Import..."; 
     _importButton.AutoSize = true; 
     _importButton.Visible = false; 

    // Add the button as a child control of this ListBox 
     Controls.Add(_importButton); 
    } 


    protected override void OnDrawItem(DrawItemEventArgs e) 
    { 
     if (this.Items.Count > 0) 
     { 
      e.DrawBackground(); 

    // draw item here (omitted) 

    // if drawing the selected item, re-position the "Import" button so that appears 
    // inside the current item, and make it visible if it is hidden. 
    // These checks prevent the resulting repaint that will occur from causing an infinite loop 

    // The problem seems to be that if the ListBox is scrolled such that the selected item 
    // moves off-screen , this code won't run, because it won't repaint the selected item anymore... 
    // This means the button will be painted in its previous position. 

    // The real question is: Is there a better way to approach the whole notion of 
    // rendering buttons within ListBox items? 

      if (e.State & DrawItemState.Selected == DrawItemState.Selected) 
      { 
       _importButton.Top = e.Bounds.Bottom - _importButton.Height - 20; 
       _importButton.Left = e.Bounds.Left; 
       if(!_importButton.Visible) _importButton.Visible = true; 
      } 
     } 

     base.OnDrawItem(e); 
    } 

    protected override void OnMeasureItem(MeasureItemEventArgs e) 
    { 
     base.OnMeasureItem(e); 
     e.ItemHeight = 100; //hard-coded for now... 
    } 

} 

爲什麼要使用一個按鈕

我寧願在ListBox創建一個單獨的按鈕,每個項目,但我無法找到任何方式來跟蹤何時添加/從列表框中刪除項目。我無法找到任何可以覆蓋的相關ListBox方法,並且我無法將ListBox.Items屬性重新分配給自定義集合對象,因爲ListBox.Items是隻讀的。因此,我採用上述方法使用單個按鈕並根據需要對其進行重新定位,但正如我所提到的,這不是非常健壯且容易中斷。

我目前的想法是,在將新項目添加到ListBox時創建新按鈕時最有意義,並且在刪除項目時刪除按鈕。

下面是我提出的一些可能的解決方案,但有沒有更好的方法來實現這一點?

  • 我可以直接創建我的派生類ListBox我自己AddItemRemoveItem方法。每次添加新項目時,我都可以創建相應的按鈕,並在RemoveItem中刪除項目的按鈕。然而,對我而言,這是一個醜陋的黑客攻擊,因爲它迫使我稱這些特殊的添加/刪除方法,而不是僅僅使用ListBox.Items
  • 我可以在OnDrawItem中使用System.Windows.Forms.ButtonRenderer手動繪製一個新按鈕,但之後我必須做很多額外的工作才能使它像一個真正的按鈕一樣,因爲ButtonRenderer只不過是在繪製按鈕而已。弄清楚用戶何時懸停在這個「按鈕」上,以及何時點擊,看起來好像很難得到。
  • 當調用OnDrawItem時,如果所繪製的項目還沒有與之關聯的按鈕,我可以創建一個新按鈕(我可以使用Dictionary<Item, Button>跟蹤此項),但我仍然需要一種方法來刪除未使用的按鈕的相應項目從列表中刪除。我想我可以迭代我的項目按鈕映射字典,並刪除ListBox中不存在的項目,但隨後我在重繪(確認!)ListBox中的每個項目時重複兩個列表。

那麼,有沒有更好的方法在ListBox內包含可點擊的按鈕?這顯然是之前完成的,但我在Google上找不到任何有用的東西。我見過的只有ListBox中有按鈕的例子都是WPF的例子,但我正在尋找如何使用WinForms來做到這一點。

回答

0

一個可能的解決

我找到了一個可行的解決方案,並最終保持單一按鈕辦法現在。我會在這裏發佈我的解決方法,但是如果任何人對我的原始問題有更優雅的回答,請不要猶豫發佈它。

我小的頓悟

嘗試一些後,似乎按鈕的問題沒有得到上只滾動使用鼠標滾輪通過ListBox滾動發生時正確定位。滾動「正常」方式似乎沒有重現行爲,但由於我無法100%確定,所以我在我的解決方法中也包含了正常滾動的修復程序。

我的解決方法

因爲我知道鼠標滾輪(和滾動一般)的醜陋的黑客似乎是在問題的心臟,我決定只無效我ListBox每當ListBox滾動或者每當鼠標滾輪移動時。這確實產生了一些難看的閃爍,我想擺脫它,但我現在可以與結果一起生活。

我添加下面的方法來我的派生類EventListBox

protected override void WndProc(ref Message m) 
    { 
     const int WM_VSCROLL = 277; 

     if (m.Msg == WM_VSCROLL) 
     { 
      this.Invalidate(); 
     } 

     base.WndProc(ref m); 
    } 

    protected override void OnMouseWheel(MouseEventArgs e) 
    { 
     this.Invalidate(); 
     base.OnMouseWheel(e); 
    } 

我有點驚訝,ListBox不從ScrollableControl繼承,而且沒有像OnScroll的方法,我可以覆蓋任何東西,所以我通過覆蓋WndProc方法做了滾動檢查。鼠標滾輪檢測更簡單,因爲已經有一種方法可以覆蓋。

就像我說過的,這樣做並不理想,因爲每次列表滾動時導致列表框失效都會導致閃爍,而且我懷疑當列表框中包含很多項目時,性能會顯着降低。

我不會接受這個答案,因爲我很好奇如果有人有更好的解決方案。

相關問題