代码之家  ›  专栏  ›  技术社区  ›  James

组合框中的名称-值对

  •  17
  • James  · 技术社区  · 16 年前

    我确信这一定是一个常见的问题,但我似乎找不到一个简单的解决办法。。。

    我想使用名值对作为项的组合框控件。ComboBox将TStrings作为它的项,这样就可以了。

    不幸的是,组合框上的drawing方法会绘制项[i],所以在框中会得到Name=Value。

    4 回复  |  直到 16 年前
        1
  •  14
  •   Andreas Rejbrand    16 年前

    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;
    
        2
  •  12
  •   Marjan Venema    11 年前

      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]);
    

    这种方法也可以用于其他对象的列表。例如,包含TPerson对象实例的TObjectList:

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

    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;
    

    将列表加载到组合框现在是一个简单的赋值:

    // 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;
    

    或从组合框:

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

    在我的博客上可以找到同样的信息和更全面的解释: TL;DR version of Name Value Pairs in ComboBoxes and Kinfolk

        3
  •  1
  •   Warren P    16 年前

    让我们考虑一下将来某人将如何维护您的应用程序。你可能会自己回到这个代码,然后想,“我在想什么?”。记住“最小惊奇原则”。用他们本该用的方式来使用东西,让你自己和你的同事免于痛苦。

        4
  •  1
  •   binar    14 年前

    我同意Marjan Venema的解决方案,因为它使用已经内置的支持在TStringList中存储对象。

    我也处理过这个问题,我首先使用上面发布的带有“csOwnerDrawFixed”的解决方案的调整版本派生出了自己的组合框组件。我实际上需要存储一个ID(通常来自数据库)和一个文本。ID将对用户隐藏。我认为这是一种常见的情况。ItemIndex只用于从列表中检索数据,它实际上不是一个有意义的变量,如上面发布的示例所示。

    所以,我制作的第二个版本使用了AddObject方法。它用CPU来换取更多的内存消耗,但这是一个公平的交易,因为事情要快得多。

    用户看到的文本通常存储在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;
    

    在这两个版本中,所有数据(甚至ID)都表示为字符串,因为它使事情更一般,所以在使用它们时,需要进行大量stroint转换,反之亦然。

    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
    

    使用相同的原理,还可以派生自定义列表框或复选框。

    推荐文章