Я пишу простой пользовательский элемент управления ListBox
, нарисованный владельцем, и стучу головой о стену, пытаясь понять, как реализовать функцию, похожую на прямолинейную. Мой пользовательский ListBox
должен работать аналогично списку «Установка и удаление программ» в Windows XP. Таким образом, он отображает список элементов как обычно, но когда пользователь выбирает элемент в списке, рядом с текстом элемента должна появиться нажимаемая кнопка. В моем случае я пытаюсь отобразить кнопку «Импорт» рядом с каждым элементом в моем ListBox
.
Чтобы пользовательский 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
и удалить кнопки, когда элементы были удалены.
Вот некоторые возможные решения, которые я придумал, но есть ли лучший способ реализовать это?
- Я мог бы просто создать свои собственные
AddItem
и RemoveItem
методы непосредственно в моем производном ListBox
классе. Я мог бы создать соответствующую кнопку каждый раз, когда добавляется новый элемент, и удалить кнопку элемента в RemoveItem
. Однако, для меня это уродливый хак, потому что он заставляет меня вызывать эти специальные методы добавления / удаления, а не просто использовать ListBox.Items
.
- Я мог бы нарисовать новую кнопку вручную в
OnDrawItem
, используя System.Windows.Forms.ButtonRenderer
, но затем мне нужно было проделать большую дополнительную работу, чтобы она действовала как настоящая кнопка, так как ButtonRenderer
не делает ничего, кроме рисования кнопка. Выяснение того, когда пользователь наводит курсор на эту «кнопку» и когда на нее нажимают, кажется, что было бы трудно разобраться в этом.
- Когда вызывается
OnDrawItem
, я мог бы создать новую кнопку, если у нарисованного элемента уже нет связанной кнопки (я мог бы отслеживать это с помощью Dictionary<Item, Button>
), но мне все еще нужно способ удалить неиспользуемые кнопки, когда их соответствующие элементы удалены из списка. Я думаю, я мог бы перебрать свой словарь отображений кнопок элементов и удалить элементы, которых больше нет в ListBox
, но затем я перебираю два списка каждый раз, когда элемент в ListBox
перерисовывается (ack !).
Итак, есть ли лучший способ включить кликабельные кнопки внутри ListBox
? Очевидно, это было сделано раньше, но я не могу найти ничего полезного в Google. Единственные примеры, которые я видел с кнопками в ListBox
, были примерами WPF, но я ищу, как это сделать с помощью WinForms.