Как получить индекс выбранного элемента в ячейке StringGrid с PickList в Delphi
Компонент TStringGrid в Delphi — мощный инструмент для отображения табличных данных. Одной из его полезных функций является возможность задать для столбца стиль cbsPickList, превращая ячейки этого столбца в выпадающие списки. Однако стандартными средствами получить индекс выбранного элемента из такого списка, особенно если в списке есть повторяющиеся строки, может быть нетривиальной задачей. В этой статье мы разберем проблему и предложим эффективное решение на Object Pascal.
Описание проблемы
Представьте, что у вас есть TStringGrid, в одном из столбцов которого используется PickList. Пользователь выбирает значение из выпадающего списка в одной из ячеек. Ваша задача — получить не только текстовое значение (string), которое выбрал пользователь (это легко сделать через StringGrid.Cells[Col, Row]), но и порядковый номер (ItemIndex) этого элемента внутри самого списка PickList.
Почему это может быть важно? Например, если список содержит неуникальные значения (одинаковые строки), то знание индекса позволит точно идентифицировать, какой именно элемент был выбран.
Простая попытка получить доступ к редактору ячейки (TPickListCellEditor) и его свойству ItemIndex напрямую для каждой ячейки не сработает.
// НЕПРАВИЛЬНЫЙ ПОДХОД: Попытка получить редактор для каждой ячейки
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
Editor: TPickListCellEditor; // Это не сработает так, как ожидается
begin
Memo1.Clear;
try
for i := StringGrid1.FixedRows to StringGrid1.RowCount -1 do
begin
// ОШИБКА: Редактор существует только для АКТИВНОЙ ячейки,
// а не для всех ячеек одновременно.
// Эта строка вернет ссылку на один и тот же экземпляр редактора,
// не связанный с конкретной строкой i вне режима редактирования.
Editor := TPickListCellEditor(StringGrid1.EditorByStyle(cbsPickList));
// Следующая строка вызовет ошибку или вернет некорректные данные
Memo1.Lines.Append(Format('Строка %d: индекс=%d элемент=%s',
[i, Editor.ItemIndex, Editor.Items[Editor.ItemIndex]]));
end;
except
Memo1.Lines.Append('<<< Ошибка доступа к редактору! >>>');
end;
end;
Проблема в том, что TStringGrid использует один экземпляр редактора для всех ячеек столбца. Этот редактор активируется, перемещается и настраивается только тогда, когда пользователь начинает редактировать конкретную ячейку. В остальное время получить доступ к состоянию редактора (например, ItemIndex) для неактивной ячейки невозможно стандартными средствами. Сама сетка хранит только строковое значение ячейки в StringGrid.Cells.
Решение 1: Простой случай (уникальные значения в PickList)
Если вы уверены, что все строки в вашем PickList уникальны, то можно использовать простое решение, предложенное пользователем wp в исходном обсуждении. Вы получаете строку из ячейки и ищете ее индекс в списке PickList, ассоциированном со столбцом:
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
cellString: String;
pickedIndex: Integer;
colWithPickList: Integer; // Индекс столбца с PickList
begin
Memo1.Clear;
colWithPickList := 0; // Замените на реальный индекс вашего столбца
// Убедимся, что у столбца есть PickList
if (colWithPickList < StringGrid1.ColCount) and
Assigned(StringGrid1.Columns[colWithPickList].PickList) then
begin
try
for i := StringGrid1.FixedRows to StringGrid1.RowCount - 1 do
begin
// Получаем строковое значение из ячейки
cellString := StringGrid1.Cells[colWithPickList, i];
// Ищем индекс этой строки в PickList столбца
pickedIndex := StringGrid1.Columns[colWithPickList].PickList.IndexOf(cellString);
Memo1.Lines.Append(Format('Строка %d: индекс=%d элемент=%s',
[i, pickedIndex, cellString]));
end;
except
on E: Exception do
Memo1.Lines.Append('<<< Ошибка: ' + E.Message + ' >>>');
end;
end
else
begin
Memo1.Lines.Append(Format('<<< Ошибка: Столбец %d не имеет PickList >>>', [colWithPickList]));
end;
end;
Ограничение: Этот метод вернет индекс первого вхождения строки cellString в PickList. Если у вас есть дубликаты ("Яблоко", "Банан", "Яблоко"), и пользователь выбрал второе "Яблоко", IndexOf все равно вернет индекс первого "Яблока" (индекс 0, если считать с нуля).
Решение 2: Обработка дубликатов с использованием Objects и кастомного редактора
Чтобы надежно хранить и извлекать индекс выбранного элемента, даже при наличии дубликатов, необходимо хранить сам индекс где-то еще, помимо свойства Cells. TStringGrid предоставляет для этого удобное, но часто игнорируемое свойство — массив Objects: array[ACol, ARow] of TObject. Каждой ячейке сетки можно сопоставить объект. Мы можем "злоупотребить" этим свойством, сохраняя в нем целочисленный индекс, предварительно преобразовав его к типу TObject.
Поскольку стандартный редактор TPickListCellEditor не дает нам нужного контроля и доступа, мы заменим его на собственный редактор — обычный TComboBox, который будет скрыт на форме и будет появляться только при редактировании ячейки.
Шаги реализации:
Добавьте TComboBox на форму. Назовем его cbGridEditor. Установите его свойство Visible := False. Заполните его Items теми же значениями, что и ваш PickList (или делайте это динамически).
Добавьте переменные в класс формы для хранения координат редактируемой ячейки:
Реализуйте обработчик OnSelectEditor для TStringGrid: Этот обработчик вызывается, когда сетка собирается показать редактор для ячейки. Здесь мы подменяем стандартный редактор на наш cbGridEditor.
```pascal procedure TForm1.StringGrid1SelectEditor(Sender: TObject; ACol, ARow: Integer; var Editor: TWinControl); var storedIndex: PtrInt; // Используем PtrInt для безопасного кастинга Integer <-> TObject obj: TObject; colWithPickList: Integer; begin colWithPickList := 0; // Индекс вашего столбца с PickList
if ACol = colWithPickList then begin // Назначаем наш ComboBox редактором Editor := cbGridEditor;
// Позиционируем и показываем ComboBox поверх ячейки
cbGridEditor.BoundsRect := StringGrid1.CellRect(ACol, ARow);
cbGridEditor.Visible := True; // Делаем видимым
// Загружаем сохраненный индекс из Objects
obj := StringGrid1.Objects[ACol, ARow];
if obj = nil then // Если индекс не был сохранен (или ячейка пуста)
begin
cbGridEditor.ItemIndex := -1; // Нет выбора
// Опционально: если ячейка не пуста, но индекса нет,
// попробуем найти по тексту (для первоначального заполнения)
if StringGrid1.Cells[ACol, ARow] <> '' then
cbGridEditor.ItemIndex := cbGridEditor.Items.IndexOf(StringGrid1.Cells[ACol, ARow]);
end
else
begin
// Преобразуем TObject обратно в PtrInt (Integer)
storedIndex := PtrInt(obj);
// Устанавливаем ItemIndex в ComboBox
// Важно: Добавляем 1 к индексу при сохранении и вычитаем при чтении,
// чтобы отличить индекс 0 от nil (не сохранено).
cbGridEditor.ItemIndex := stored
В статье рассматривается проблема получения индекса выбранного элемента из ячейки StringGrid с PickList в Delphi и предлагаются два решения: для уникальных значений в PickList с использованием IndexOf и для обработки дубликатов с использованием свойства
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.