Ошибка обнаружена в Delphi 5 и 6, причем в Delphi 5 попытка ввести значение из указанного диапазона также может привести к ошибке и в режиме проектирования, при переключении между текстом модуля и формой. В Delphi 6 были замечены ошибки только при запуске программы, в режиме проектирования они не возникали. В Delphi 7 эта проблема уже решена, указанные значения свойств не приводят к ошибкам. В более ранних версиях Delphi проблема, естественно, также отсутствует, потому что в них dfm-файл всегда представляется в бинарной форме.
Для решения проблемы могут быть рекомендованы два способа.
1. Обновить Delphi до седьмой (или более поздней) версии.
2. Выбрать бинарную
форму dfm-файла. Для этого нужно щёлкнуть правой кнопкой мыши на форме и в открывшемся меню убрать галочки с пункта Text DFM.
Можно также отказаться от присвоения проблемных значений свойствам в режиме проектирования и присваивать их во время выполнения программы.
3.4.6. Ошибка List index out of bounds при корректном значении индекса
Windows позволяет с каждой строкой списка элементов управления
ListBoх
и
ComboBox
связать либо число, либо указатель (точнее — некоторую четырехбайтную величину, которую программа может трактовать как число, как указатель или как что-либо еще). В VCL эта возможность обеспечивает привязку к строкам списка объектов (четырёхбайтная величина по умолчанию трактуется как
TObject
). Доступ к этим объектам осуществляется через свойства
TComboBox.Items.Objects[Index]
и
TListBox.Items.Objects[Index]
.
Иногда все-таки требуется привязывать к строкам не объекты, а числа. Для этого можно воспользоваться явным приведением типов, например:
ComboBox1.Items.Objects[I] := TObject(17);
I := Integer(ComboBox1.Items.Objects[I]);
Если таким образом связать со строкой значение -1, то при попытке получить его во всех версиях Delphi до 7-й включительно возникнет исключение
EStringListError
с комментарием "List index out of bounds". Рассмотрим следующий код (листинг 3.56. пример ListIndex на компакт-диске).
Листинг 3.56. Пример кода, вызывающего исключение
EStringListError
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
ComboBox1.Items.Clear;
ComboBox1.Items.AddObject('Text', TObject(-1));
I := Integer(ComboBox1.Items.Objects[0]); { * }
Label1.Caption := IntToStr(I);
end;
Исключение возникнет при попытке выполнить строку, отмеченную звездочкой, хотя очевидно, что индекс в данном случае корректен. Чтобы понять причину ошибки, необходимо рассмотреть, как осуществляется чтение значения, привязанного к строке, на уровне Windows API. Рассмотрим это на примере
TComboBox
. Для получения значения необходимо послать окну
ComboBox
сообщение
CB_GETITEMDATA
. Результатом обработки этого сообщения будет значение, связанное с указанной строкой, или
CB_ERR
, если при обработке сообщения возникнет ошибка. При этом документация не уточняет, какие именно ошибки могут в принципе возникнуть и как узнать, какая из них произошла.
Метод
TComboBoxStrings.GetObject
, через который читается значение свойства
Objects
,
в Delphi 7 и более ранних версиях интерпретирует получение
CB_ERR
однозначно: генерирует исключение
EStringListError
с комментарием "List index out of bounds".
Проблема заключается в том, что константа
CB_ERR
имеет численное значение -1. Поэтому и в случае ошибки, и в случае, когда строке сопоставлено значение -1, системный обработчик сообщения
CB_GETITEMDATA
вернет одинаковый результат. И метод
TComboBoxStrings.GetObject
интерпретирует его как ошибку. (А что ему еще остается делать?)
Аналогичная проблема по тем же причинам возникает и для
ListBox
(аналогичная по смыслу константа
LB_ERR
также имеет значение -1). Это прямое следствие документированных особенностей работы Windows и модели работы VCL встречается во всех версиях Windows. Та же проблема возникает при попытке указать значение 4 294 967 295, т.к. на двоичном уровне это число записывается той же комбинацией битов, что и -1.
При использовании свойства
Objects
по прямому назначению, т.е. для хранения объектов, эта проблема не может возникнуть, потому что $FFFFFFFF — это адрес самого старшего байта в четырехгигабайтном виртуальном адресном пространстве программы. Эта область адресного пространства зарезервирована системой, и менеджер памяти Delphi не может выделить память для объекта в этой области. Рекомендуемые способы решения проблемы:
1. Пересмотреть алгоритм и отказаться от связывания значения -1 со строками.
2. Напрямую посылать
CB_GETITEMDATA
окну
ComboBox
, а попадание индекса в диапазон контролировать самостоятельно другими методами. Приведенный в листинге 3.57 код иллюстрирует последний совет.
Листинг 3.57. Получение связанного с элементом значения вручную
procedure TForm1.Button2Click(Sender: TObject);
var
I: Integer;
begin
ComboBox1.Items.Clear;
ComboBox1.Items.AddObject('Text', TObject(-1));
I := SendMessage(ComboBox1.Handle, CB_GETITEMDATA, 0, 0);
Label1.Caption := IntToStr(I);
end;
Как уже было отмечено ранее, в BDS 2006 и более поздних версиях исключение не возникает. Это связано с новой реализацией метода
TCustomComboBoxStrings.GetObject
, который отвечает за получение значения свойства
Items.Object
(листинг 3.58).
Листинг 3.58. Получение значения свойства
Items.Object
в BDS 2006 и выше
function TCustomComboBoxStrings.GetObject(Index: Integer): TObject;
begin
Result := TObject(SendMessage(ComboBox.Handle, CB_GETITEMDATA, Index, 0));
// Do additional checking on Count and Index here is so in the event
// the object being retrieved is the integer -1 the call will succeed