.NET WinForms ComboBox, идентичные элементы и событие SelectedIndexChanged - PullRequest
5 голосов
/ 10 декабря 2008

Кажется, что когда у вас есть приложение WinForms .NET и ComboBox (установлен стиль «DropDown»), а ComboBox содержит несколько одинаковых элементов, происходят странные вещи. В частности, индекс выбранного элемента может измениться без , вызвав событие SelectedIndexChanged.

Конечно, это вызывает массовое замешательство и странные, неясные ошибки, из-за которых я недавно вырвал свои волосы.

Вот простой пример, который вы можете использовать, чтобы увидеть, о чем я говорю:

  • Создайте новый проект .NET WinForms (я использую VB.NET, но не стесняйтесь переводить - это достаточно просто).
  • Перетащите ComboBox, кнопку и TextBox (установите MultiLine = True) на форму.
  • Используйте следующий код для загрузки ComboBox с 3 одинаковыми элементами и для печати некоторых сообщений о состоянии при возникновении события SelectedIndexChanged, а также для просмотра текущего выбранного индекса (с помощью кнопки):
Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
        TextBox1.Text = TextBox1.Text & vbNewLine & "ComboBox SelectedIndexChanged event fired." & vbNewLine & _
            "SelectedIndex is: " & ComboBox1.SelectedIndex
    End Sub

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        ComboBox1.Items.Add("John Doe")
        ComboBox1.Items.Add("John Doe")
        ComboBox1.Items.Add("John Doe")

    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        TextBox1.Text = TextBox1.Text & vbNewLine & _
        "Button clicked." & vbNewLine & _
        "SelectedIndex is: " & ComboBox1.SelectedIndex
    End Sub

Запустите проект и выберите элемент в ComboBox (скажем, средний). Затем щелкните стрелку раскрывающегося списка ComboBox, но НЕ ВЫБИРАЙТЕ НИЧЕГО. Нажмите кнопку (по умолчанию Button1) и посмотрите, что она говорит.

Если я не сошел с ума, вот что вы должны увидеть:

ComboBox SelectedIndexChanged event fired.
SelectedIndex is: 1
Button clicked.
SelectedIndex is: 0

Другими словами, ИЗБРАННЫЙ ИНДЕКС ИЗМЕНЕН, но без срабатывания события SelectedIndexChanged!

Это происходит только тогда, когда элементы в ComboBox идентичны. Если они разные, этого не происходит. (Этого также не происходит, если стиль «DropDown» в ComboBox установлен на «DropDownList».)

Я подозреваю, что это может быть ошибка в самой платформе .NET, и не то, что я могу исправить, но есть вероятность, что у кого-то еще есть какие-либо идеи о том, что делать здесь (или что я могу делать неправильно!), пожалуйста, перезвоните! Я затрудняюсь объяснить это поведение или обойти его (я ожидаю, что SelectedIndex останется прежним, если, вы знаете, вы фактически НЕ ИЗМЕНИТЕ его, выбрав что-то еще!)

Ответы [ 4 ]

17 голосов
/ 10 декабря 2008

.NET Framework фактически не отслеживает выбранный индекс выпадающего списка поля со списком; это обрабатывается внутри Windows API. Вследствие этого .NET зависит от Windows API, чтобы уведомлять его при изменении выбранного индекса посредством уведомления, отправляемого в дескриптор окна комбинированного окна, так что оно может, в свою очередь, запускать событие SelectedIndexChanged.

К сожалению, оказывается, что конкретное уведомление, которое отслеживает .NET (точнее, CBN_SELCHANGE), НЕ охватывает все возможные сценарии, в которых может измениться выбранный индекс. В частности, CBN_SELCHANGE отправляется только Windows API, если пользователь щелкает или выбирает с помощью клавиш со стрелками элемент в раскрывающемся списке. Однако в поле со списком в стиле DropDown при открытии поля со списком Windows просматривает текст в редактируемой части поля со списком, выполняет поиск в списке элементов на соответствие и, если совпадение найдено, автоматически выберите соответствующий элемент (или первый соответствующий элемент, если имеется несколько соответствующих элементов). Это может изменить выбранный индекс, но НЕ отправляет уведомление CBN_SELCHANGE, поэтому .NET пропускает тот факт, что он изменился, и не вызывает событие SelectedIndexChanged.

Windows делает все это в поле со списком в стиле DropDown, потому что пользователь НЕ ДОЛЖЕН выбирать что-либо в выпадающем списке; они могут печатать все, что хотят. Поэтому каждый раз, когда вы открываете поле со списком, предполагается, что пользователь мог изменить текст, и пытается выполнить повторную синхронизацию с тем, что в списке, если это возможно.

В вашем случае, когда вы открываете поле со списком во второй раз, он выполняет повторную синхронизацию и выбирает первое совпадение для текста в части редактирования, которая является "John Doe" # 0, и изменяет выбранный индекс до 0, но .NET не знает.

Так что это в основном ошибка в .NET Framework. К сожалению, нет идеального обходного пути - вы не можете заставить Windows не выполнять повторную синхронизацию, и нет события, которое запускается сразу после повторной синхронизации, в котором вы можете получить новый выбранный индекс. (Событие DropDown фактически срабатывает непосредственно перед повторной синхронизацией, поэтому он не увидит новый индекс.) Самое лучшее, что вы можете сделать, - это обработать событие DropDownClosed, предположить, что индекс мог измениться в этот момент, и действовать соответствующим образом. .

2 голосов
/ 30 марта 2009

Ответ Эрика был очень тщательным, но я был удивлен, увидев, что он не заканчивается словами «... но на самом деле вы должны спросить себя, почему вы заполняете поле со списком дубликатами». Ошибка платформы .Net, без сомнения, была допущена к существованию, потому что когда вы используете элемент управления по назначению, чтобы позволить пользователю выбирать элемент из списка, вы не сталкиваетесь с этой ошибкой.

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

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

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

1 голос
/ 29 мая 2009

В некоторых случаях наличие дублирующих элементов в списке не только допустимо, но и желательно. Обратите внимание на поле со списком OpenFileDialog, которое вы видите в Visual Studio при нажатии кнопки «Открыть файл». Это показывает комбинированное окно с такими элементами, как «Мой компьютер», «Рабочий стол», «Мои документы» и т. Д. Для имен папок в списке присутствует только краткое имя. Полный путь не отображается. И поэтому вполне возможно, что папка имеет такое же (короткое) имя, что и один из ее потомков.

Итак, представьте следующую структуру папок:

C:\
C:\A
C:\A\B
C:\A\B\A

Идеальная структура. В моей реализации я установил свойство DataSource в BindingList объектов. ValueMember объекта - это полное имя файла, а DisplayMember - краткое имя файла. Поле со списком должно отображать:

C:\
    A
        B
            A

Совершенно хороший дизайн пользовательского интерфейса. Отступы предполагают вложение папок.

Но когда я устанавливаю значение SelectedValue поля со списком «C: \ A \ B \ A», выбирается неправильный элемент. Элемент, который должен быть выбран, является последним (4-й элемент) в списке, но вместо этого выбирается 2-й элемент (индекс 1). И установка SelectedIndex = 3 не ведет себя как задумано. Опять же, выбран второй элемент, а не последний.

То, что здесь происходит, заключается в том, что при установке SelectedValue или SelectedIndex значение преобразуется с использованием свойства DisplayMember, и элемент управления выполняет поиск соответствия от начала до конца. Поиск должен выполняться с использованием свойства ValueMember. Пример кода ниже. Признателен, если кто-то может подтвердить, что это ошибка или что-то, что я сделал неправильно.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace ComboBoxTest
{
   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();
      }

      private void Form1_Load(object sender, EventArgs e)
      {
         if (DesignMode)
            return;

         BindingList<CBItem> items = new BindingList<CBItem>();
         items.Add(new CBItem("A", @"C:\A"));
         items.Add(new CBItem("B", @"C:\A\B"));
         items.Add(new CBItem("A", @"C:\A\B\A"));

         comboBox.DisplayMember = "DisplayValue";
         comboBox.ValueMember = "RealValue";
         comboBox.DataSource = items;

         comboBox.SelectedValue = @"C:\A\B\A";
      }
   }

   class CBItem
   {
      public CBItem(string displayValue, string realValue)
      {
         _displayValue = displayValue;
         _realValue = realValue;
      }

      private readonly string _displayValue, _realValue;

      public string DisplayValue { get { return _displayValue; } }
      public string RealValue { get { return _realValue; } }
   }
}
0 голосов
/ 28 июня 2009

Похожая проблема возникает без идентичных элементов, если вы вводите произвольный текст, который не совпадает точно с первыми символами. Если пользователь не открывает раскрывающийся список, повторная синхронизация не происходит, и выбранный индекс равен -1, как и ожидалось. (Не выбор одного из пунктов - это то, что пользователь собирается делать) Теперь пользователь закрывает диалоговое окно и открывает его снова. Вы, как программист, восстанавливаете комбинированный список с текстом, введенным пользователем, и текст автоматически заполняется до частичного соответствия элемента без запуска события. Если пользователь закрывает диалоговое окно, текст изменяется без уведомления. Эта проблема не возникает, если текст не соответствует ни одному элементу.

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