これは一般的な問題であるに違いないと私は確信していますが、簡単な解決策を見つけることができないようです...
名前と値のペアをアイテムとして持つコンボボックスコントロールを使用したいと思います。 ComboBoxは、アイテムとしてTStringを使用するため、問題はありません。
残念ながら、コンボボックスの描画メソッドはItems [i]を描画するため、ボックスにName = Valueが表示されます。
値を非表示にして、コードで値を操作できるようにしたいのですが、ユーザーには名前が表示されます。
何か案は?
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;
値が整数の場合:名前と値のペアを分割し、コンボボックスの文字列に名前を格納し、対応するオブジェクトに値を格納します。
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]);
選択したアイテムの対応する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;
D2010より前のバージョンのDelphiを使用している場合は、TValueの代わりにstring
を使用してください。
リストの前処理:
// 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;
またはComboBoxから:
begin
Name_Text.Caption := ComboBox.Items[idx];
Value_Text.Caption := TValueObject(ComboBox1.Items.Objects[idx]).Value.AsString;
end;
より包括的な説明を含む同じ情報が私のブログにあります: TL; ComboBoxesとKinfolkの名前と値のペアのDRバージョン
TStringListにオブジェクトを格納するための組み込みのサポートを使用しているため、MarjanVenemaのソリューションに同意します。
私もこれに対処し、最初に「csOwnerDrawFixed」を使用して上記の投稿されたソリューションの微調整バージョンを使用して独自のコンボボックスコンポーネントを派生させました。実際には、ID(通常はデータベースから)をテキストと一緒に保存する必要がありました。 IDはユーザーから隠されます。これは一般的なシナリオだと思います。 ItemIndexは、リストからデータを取得するためだけに使用されます。上記の投稿例のように、実際には意味のある変数ではありません。
したがって、私のアイデアは、IDを表示されたテキストと連結し、たとえば「#」で区切ってDrawItem()をオーバーライドし、IDが削除されたテキストのみをペイントするようにすることでした。これを拡張して、ID以上のものを「Name#ID; var1; var2」の形式で保持します。 「マイケル・サイモンズ#11; true; M」。 DrawItem()は、#の後のすべてを削除します。
コンボにアイテムがほとんどない場合は、最初にこれで十分です。ただし、より大きなリストを処理する場合、コンボをスクロールするとCPUが集中的に使用されます。これは、アイテムを描画するたびに、テキストを削除する必要があるためです。
したがって、私が作成した2番目のバージョンは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も含む)は文字列として表されます。これは、物事をより一般的に保つためです。したがって、それらを使用する場合は、多くの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
iDのインデックスを取得するには、この方法が便利ですが、この回答はすでに長すぎてここに投稿できません。
同じ原則を使用して、カスタムリストボックスまたはチェックリストボックスを派生させることもできます。
コンボボックスアイテムのテキストには、表示テキストが含まれている必要があります。それが適切なスタイルです。次に、ItemIndexプロパティを使用して、内部キー値を検索します。コントロールのプロパティを無効にしてモデルコードまたはデータベースの内部キー値を含めることは、OOPの原則に大きく違反します。
誰かが将来あなたのアプリケーションをどのように維持するのかを考えてみましょう。自分でこのコードに戻って、「私は何を考えていたのか」と考えるかもしれません。 「最も驚きの少ない原則」を忘れないでください。本来の使用方法で物事を使用し、自分自身と同僚を苦痛から救ってください。