-2

Does a TDbComboBox-like control exist, that gets its displayed values by an fixed list (to use for an enumerated type, e.g. TSomeValueEnum = (svSmall=1, svMedium=2, svLarge=3))?

With for instance:

1=small
2=medium
3=large

In the database I save 1 or 2 or 3, but in the ComboBox only the corresponding value should be displayed.

Alois Heimer
  • 1,772
  • 1
  • 18
  • 40
  • I thought saving enumerated types in a database and displaying them via a kind of TDbComboBox should be a common task. Therefore I wonder why there does not seem to exist a builtin or free component implementing this behaviour. – Alois Heimer Jul 28 '14 at 12:25
  • I just found a very similar question [here](http://stackoverflow.com/q/299548/2523663). – Alois Heimer Jul 29 '14 at 08:02

3 Answers3

3

Raize Components has a TRzDbComboBox where you have separate lists for Items and Values.

From the help:

The TRzDBComboBox does support a Values property, which can be used to define a list of associated values to be stored in the selected database field instead of the string values maintained in the Items list. For example, the Items list could be set up to contain the following items: Visa, MasterCard, American Express; while the Values list would contain the following values: VISA, MC, AMEX. If the user selected the American Express item in the drop down, the AMEX value would be stored in the database table.

Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • I'm glad you mentioned Raize because I was just reminding myself how awkward it is to try and persuade a regular (DB) combobox to have something as its text which differs from one of its items. I'll whittle my answer down a bit! – MartynA Jul 27 '14 at 14:34
  • Thanks, this is exactly, what I was looking for. It's a pity, I do not have a license for the component set - but for this minor problem I think it will not be worth it. I hoped, there would exist a builtin solution. But I think your answer is the closest fit for my original question. – Alois Heimer Jul 29 '14 at 21:42
2

You can use a TDbLookupComboBox combined with a memory-table (fi TClientDataSet). Fill the memory-table with the desired values and the TDbLookupComboBox will do the rest.

But there is one missing link, between the stored value in database, the string representation for the UserInterface and the Enum for your application. To put all together in one place you should build a class that will handle all this conversion for you in a convenient, safe and documented-by-code way.

TSomeValueEnum = (svSmall, svMedium, svLarge);

TSomeValue = class
private
  FAsInteger : Integer;
  FAsEnum : TSomeValueEnum;
  FAsString : string;
public
  // returns a new created list with all values
  class function CreateAsList : TObjectList;

  // constructors

  constructor Create( Value : Integer ); overload;
  constructor Create( Value : TSomeValueEnum ); overload;
  constructor Create( const Value : string ); overload;

  // equal comparer with other values    
  function Equals( Obj : TObject ) : Boolean; override;
  function SameValueAs( Other : TSomeValue ) : Boolean;

  // properties
  property AsEnum : TSomeValueEnum read FAsEnum;
  property AsInteger : Integer read FAsInteger;
  property AsString : string read FAsString;

  // Same properties but with different names, just for clarification

  // Value used in Database
  property DbValue : Integer read FAsInteger;
  // Value used for UserInterface
  property UIValue : string read FAsString;
  // Value used inside the applicatiom
  property AppValue : TSomeValueEnum read FAsEnum;
end;

implementation

type
  TSomeValueRec = record
    Int : Integer;
    Str : string;
  end;

// Translation-Array for DbValue and UIValue
const
  C_SomeValues : array[TSomeValueEnum] of TSomeValueRec = ( 
    {svSmall}  (Int:1; Str:'small'), 
    {svMedium} (Int:2; Str:'medium'), 
    {svLarge}  (Int:3; Str:'large') );

function TSomeValue.Equals( Obj : TObject ) : Boolean;
begin
  Result := ( Self = Obj ) or Assigend( Obj ) and (Self.ClassType = Obj.ClassType) and SameValueAs( Obj as TSomeValue );
end;

function TSomeValue.SameValueAs( Other : TSomeValue ) : Boolean;
begin
  Result := ( Self = Other ) or Assigned(Other) and (Self.FAsEnum = Other.FAsEnum);
end;

constructor Create( Value : Integer );
var
  LEnum : TSomeValueEnum;
begin
  inherited Create;
  for LEnum := Low(LEnum) to High(LEnum) do
    if C_SomeValues[LEnum].Int = Value then
      begin
        FAsEnum := LEnum;
        FAsInteger := C_SomeValues[LEnum].Int;
        FAsString := C_SomeValues[LEnum].Str;
        Exit;
      end;  
  raise EArgumentException.CreateFmt('unsupported value %d',[Value]);
end;

constructor Create( const Value : string );
var
  LEnum : TSomeValueEnum;
begin
  inherited Create;
  for LEnum := Low(LEnum) to High(LEnum) do
    if SameText( C_SomeValues[LEnum].Str, Value ) then
      begin
        FAsEnum := LEnum;
        FAsInteger := C_SomeValues[LEnum].Int;
        FAsString := C_SomeValues[LEnum].Str;
        Exit;
      end;  
  raise EArgumentException.CreateFmt('unsupported value "%s"',[Value]);
end;

constructor Create( Value : TSomeValueEnum );
begin
  inherited Create;
  FAsEnum := Value;
  FAsInteger := C_SomeValues[Value].Int;
  FAsString := C_SomeValues[Value].Str;
end;

class function TSomeValue.CreateAsList : TObjectList;
var
  LEnum : TSomeValueEnum;
begin
  Result := TObjectList.Create( True );
  for LEnum := Low(TSomeEnum) to High(TSomeEnum) do
    Result.Add( Self.Create( LEnum ) );
end;

To fill up the memory table get the list from TSomeValue.CreateAsList and fill the table from that list.

Alois Heimer
  • 1,772
  • 1
  • 18
  • 40
Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
  • +1 I like your code for handling enums in object oriented way. But it would be way more useful if TSomeValueEnum could/would be handled in a generic way. Your hint regarding TClientDataSet is useful as well. – Alois Heimer Jul 28 '14 at 11:21
  • If you upgrade to a newer Delphi version, then it is possible to have a generic for that :) – Sir Rufo Jul 28 '14 at 12:25
  • Good to know. I'm planning to upgrade and generics is the language feature I miss the most right now. Thanks for your answer. – Alois Heimer Jul 28 '14 at 12:31
  • We already have just an answer for that here http://stackoverflow.com/questions/24955704/how-to-populate-memtables-from-enumarations/24957563#24957563 – Sir Rufo Jul 28 '14 at 12:34
0

I ended up implementing the following workaround (using Firebird as database, other implementations will differ):

QueryForComboBox.SQL.Text := 
  'SELECT 1 as VAL, ''small'' as TXT FROM RDB$DATABASE'
  + ' UNION ALL SELECT 2 as VAL, ''medium'' as TXT FROM RDB$DATABASE'
  + ' UNION ALL SELECT 3 as VAL, ''large'' as TXT FROM RDB$DATABASE';
//...
ComboBox.KeyField := 'VAL';
ComboBox.ListField := 'TXT';

For my little problem, this seems to be enough. For "real code" I think, I would prefer a TClientDataset-based solution (as proposed by Sir Rufo)

An other alternative would be to use TDbComboBox with custom drawing as described here.

Community
  • 1
  • 1
Alois Heimer
  • 1,772
  • 1
  • 18
  • 40