Как лучше всего рисовать «дочерние» элементы управления (например, кнопки) внутри каждого элемента в WinForms ListBox? - PullRequest
1 голос
/ 16 апреля 2009

Я пишу простой пользовательский элемент управления 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.

1 Ответ

0 голосов
/ 16 апреля 2009

Одно возможное решение

Я нашел работоспособное решение и в итоге придерживался подхода с одной кнопкой. Я опубликую свой обходной путь здесь, но если у кого-то есть более элегантный ответ на мой оригинальный вопрос, не стесняйтесь публиковать его.

Мое маленькое прозрение

После еще нескольких экспериментов кажется, что проблема с неправильным позиционированием кнопки при прокрутке возникает только при использовании колесика мыши для прокрутки 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 , Обнаружение колеса мыши было проще, поскольку уже был доступен метод для переопределения.

Как я уже сказал, это менее чем идеально, поскольку аннулирование списка при каждой прокрутке списка вызывает мерцание, и я подозреваю, что производительность будет значительно снижаться, если список содержит много элементов.

Я пока не приму этот ответ, так как мне любопытно посмотреть, есть ли у кого-нибудь лучшее решение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...