Имя Значение Пары в ComboBox - PullRequest
17 голосов
/ 11 июня 2010

Я убежден, что это обычная проблема, но я не могу найти простое решение ...

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

К сожалению, метод рисования в выпадающем списке рисует Предметы [i], поэтому вы получаете Имя = Значение в поле.

Мне бы хотелось, чтобы значение было скрыто, чтобы я мог работать со значением в коде, но пользователь видит имя.

Есть идеи?

Ответы [ 4 ]

14 голосов
/ 11 июня 2010

Установите Style в csOwnerDrawFixed и напишите

procedure TForm1.ComboBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
  ComboBox1.Canvas.TextRect(Rect, Rect.Left, Rect.Top, ComboBox1.Items.Names[Index]);
end;
11 голосов
/ 11 июня 2010

Если ваши значения являются целыми числами: разделите пары «имя-значение», сохраните имена в строках выпадающего списка и значения в соответствующих объектах.

  for i := 0 to List.Count - 1 do
    ComboBox.AddItem(List.Names[i], TObject(StrToInt(List.ValueFromIndex[i], 0)));

Таким образом, вы можете продолжать использовать элементы управления вуниверсальный способ и все еще имеет значение, доступное через:

Value := Integer(ComboBox.Items.Objects[ComboBox.ItemIndex]);

Этот подход также может использоваться для списков других объектов.Например, TObjectList, содержащий экземпляры объекта TPerson:

var
  i: Integer;
  PersonList: TObjectList;
begin
  for i := 0 to PersonList.Count - 1 do
    ComboBox.AddItem(TPerson(PersonList[i]).Name, PersonList[i]);

и извлечение соответствующего TPerson выбранного элемента с помощью:

Person := TPerson(ComboBox.Items.Objects[ComboBox.ItemIndex]);

Обновление

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

Простой - расширенный RTTI на основе - класс-оболочка:

type
  TValueObject = class(TObject)
  strict private
    FValue: TValue;
  public
    constructor Create(const aValue: TValue);
    property Value: TValue read FValue;
  end;

  { TValueObject }

constructor TValueObject.Create(const aValue: TValue);
begin
  FValue := aValue;
end;

Если вы используете версию Delphi до D2010, просто используйте string вместо TValue.

Предварительная обработка списка:

// Convert the contents so both the ComboBox and Memo can show just the names
// and the values are still associated with their items using actual object
// instances.
for idx := 0 to List.Count - 1 do
begin
  List.Objects[idx] := 
    TValueObject.Create(List.ValueFromIndex[idx]);

  List.Strings[idx] := List.Names[idx];
end;

Загрузка списка в Combo теперь является простым заданием:

// Load the "configuration" contents of the string list into the combo box
ComboBox.Items := List; // Does an Assign!

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

Получение имени и значения из списка:

begin
  Name_Text.Caption := List.Items[idx];
  Value_Text.Caption := TValueObject(List.Objects[idx]).Value.AsString;
end;

или из ComboBox:

begin
  Name_Text.Caption := ComboBox.Items[idx];
  Value_Text.Caption := TValueObject(ComboBox1.Items.Objects[idx]).Value.AsString;
end;

Та же информация с болееКомпрессорыподробное объяснение можно найти в моем блоге: TL; DR-версия пар «имя-значение» в ComboBoxes и Kinfolk

1 голос
/ 19 декабря 2011

Я согласен с решением Marjan Venema, так как оно использует уже встроенную поддержку для хранения объектов в TStringList.

Я также имел дело с этим, и я сначала получил свой собственный компонент со списком, используя настроенную версиюиз вышеупомянутого решения с "csOwnerDrawFixed".Мне действительно нужно было хранить идентификатор (обычно из базы данных) вместе с текстом.Идентификатор будет скрыт от пользователя.Я думаю, что это распространенный сценарий.ItemIndex используется только для извлечения данных из списка, на самом деле он не является значимой переменной, как в приведенном выше примере.

Поэтому я решил объединить идентификатор с отображаемым текстом, разделенным знаком "# ", например, и переопределить DrawItem (), чтобы он рисовал текст только с разделенным идентификатором.Я расширил это, чтобы сохранить больше, чем идентификатор, в форме «Имя # ID; var1; var2», например."Майкл Симонс # 11; правда; М".DrawItem () удалит все после #.

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

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

Текст, который видит пользователь, обычно хранится в combo.Items, а все остальные данные хранятся вTStringList связан с каждым элементом.Нет необходимости переопределять DrawItem, поэтому вы можете получить, например, из.TmxFlatComboBox и сохранить его плоский вид как есть.

Вот некоторые из наиболее важных функций производного компонента:

procedure TSfComboBox.AddItem(Item: string; Lista: array of string);
var ListaTmp: TStringList;
    i: integer;
begin
  ListaTmp:= TStringList.Create;
  if High(Lista)>=0 then
    begin
    for i:=0 to High(Lista) do
      ListaTmp.Add(Lista[i]);
    end;
  Items.AddObject(Item, ListaTmp);  
  //ListaTmp.Free; //no freeing here! we override .Clear() also and the freeing is done there
end;

function TSfComboBox.SelectedId: string;
begin
  Result:= GetId(ItemIndex, 0);
end;

function TSfComboBox.SelectedId(Column: integer): string;
begin
  Result:= GetId(ItemIndex, Column);
end;

function TSfComboBox.GetId(Index: integer; Column: integer = 0): string;
var ObiectTmp: TObject;
begin
  Result:= '';
  if (Index>=0) and (Items.Count>Index) then
    begin
    ObiectTmp:= Items.Objects[Index];
    if (ObiectTmp <> nil) and (ObiectTmp is TStringList) then
      if TStringList(ObiectTmp).Count>Column then
        Result:= TStringList(ObiectTmp)[Column];
    end;
end;

function TSfComboBox.SelectedText: string;
begin
  if ItemIndex>=0
    then Result:= Items[ItemIndex]
    else Result:= '';    
end;

procedure TSfComboBox.Clear;
var i: integer;
begin
  for i:=0 to Items.Count-1 do
    begin
    if (Items.Objects[i] <> nil) and (Items.Objects[i] is TStringList) then
      TStringList(Items.Objects[i]).Free;
    end;
  inherited Clear;
end;

procedure TSfComboBox.DeleteItem(Index: Integer);
begin
  if (Index < 0) or (Index >= Items.Count) then Exit;
  if (Items.Objects[Index] <> nil) and (Items.Objects[Index] is TStringList) then
    TStringList(Items.Objects[Index]).Free;
  Items.Delete(Index);
end;

В обеих версиях все данные (даже идентификаторы) представлены в виде строкпотому что он хранит вещи более общего характера, поэтому при их использовании вам нужно выполнить много преобразований StrToInt и наоборот.

Пример использования:

combo1.AddItem('Michael Simons', ['1', '36']); // not using Items.Add, but .AddItem !
combo1.AddItem('James Last', ['2', '41']);
intSelectedID:= StrToIntDef(combo1.SelectedId, -1); // .ItemIndex would return -1 also, if nothing is selected
intMichaelsId:= combo1.GetId(0);
intMichaelsAge:= combo1.GetId(0, 1); // improperly said GetId here, but you get the point
combo1.Clear; // not using Items.Clear, but .Clear directly !

Кроме того,

GetIndexByValue(ValueToSearch: string, Column: integer = 0): integer

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

Используя тот же принцип, вы также можете получить собственный ListBoxили CheckListBox.

1 голос
/ 14 июня 2010

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

Давайте просто рассмотрим, как кто-то будет поддерживать ваше приложение в будущем. Вы можете сами вернуться к этому коду и подумать: «о чем я думал?». Помните «принцип наименьшего изумления». Используйте вещи так, как они предназначены, и спасите себя и своих коллег от боли.

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