我正在寫一個簡單的自定義的所有者繪製的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
我自己AddItem
和RemoveItem
方法。每次添加新項目時,我都可以創建相應的按鈕,並在RemoveItem
中刪除項目的按鈕。然而,對我而言,這是一個醜陋的黑客攻擊,因爲它迫使我稱這些特殊的添加/刪除方法,而不是僅僅使用ListBox.Items
。 - 我可以在
OnDrawItem
中使用System.Windows.Forms.ButtonRenderer
手動繪製一個新按鈕,但之後我必須做很多額外的工作才能使它像一個真正的按鈕一樣,因爲ButtonRenderer
只不過是在繪製按鈕而已。弄清楚用戶何時懸停在這個「按鈕」上,以及何時點擊,看起來好像很難得到。 - 當調用
OnDrawItem
時,如果所繪製的項目還沒有與之關聯的按鈕,我可以創建一個新按鈕(我可以使用Dictionary<Item, Button>
跟蹤此項),但我仍然需要一種方法來刪除未使用的按鈕的相應項目從列表中刪除。我想我可以迭代我的項目按鈕映射字典,並刪除ListBox
中不存在的項目,但隨後我在重繪(確認!)ListBox
中的每個項目時重複兩個列表。
那麼,有沒有更好的方法在ListBox
內包含可點擊的按鈕?這顯然是之前完成的,但我在Google上找不到任何有用的東西。我見過的只有ListBox
中有按鈕的例子都是WPF的例子,但我正在尋找如何使用WinForms來做到這一點。