Метод 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 код иллюстрирует последний совет.
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).
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
if (Longint(Result) = CB_ERR) and ((Count = 0) or
(Index < 0) or (Index > Count)) then
Error(SListIndexError, Index);
end;
Решение спорное, т. к. проверка корректности системой дополняется собственной проверкой индекса, и не совсем понятно, что делать в том случае, если система фиксирует какую-либо ошибку, не связанную с индексом. Но здесь Windows ставит разработчика в такие условия, что любое решение будет спорным, так что упреком по отношению к разработчикам VCL такая оценка их решения не является.
В таких элементах управления, как TListView
и TTreeView
, тоже существует возможность связывания 4-байтного значения с элементом (см. свойства TTreeNode.Data
, TListItem.Data
), но сообщения TVM_GETITEM
и LVM_GETITEM
, через которые можно получить значения этих свойств, устроены иначе, поэтому связывание с элементом значения -1 (а также любого другого 4-байтного значения) не приводит к аналогичным проблемам.
3.4.7. Неправильное поведение свойства