wqclatre
asked on
Build a tree in a combobox.
I like to build a tree in a combobox.
I have a database with 4 fields.
I like to do a select * from table order by field1,field2,field3,field 4
Then I like to fill a combobox with the result in form of a tree.
How can I do that?
I have a database with 4 fields.
I like to do a select * from table order by field1,field2,field3,field
Then I like to fill a combobox with the result in form of a tree.
How can I do that?
ASKER
I don't like to use some special component for this.
I think that this should be posible to do with a real combobox. I Mean it is possible to move text in with:
TCustomComboBox(Control).C anvas.Text Out(Rect.L eft+18,Rec t.Top,TCom boBox(Cont rol).Items [Index])
for example.....
I think that this should be posible to do with a real combobox. I Mean it is possible to move text in with:
TCustomComboBox(Control).C
for example.....
well, this would not have the effect of an treeview,
but if it is enough for to indent the item-string by depth-level, then this may easy
can yo show some sample-data and
the result you want to have?
meikl ;-)
but if it is enough for to indent the item-string by depth-level, then this may easy
can yo show some sample-data and
the result you want to have?
meikl ;-)
ASKER
Ok say that I have a table like:
car color year combi
volvo red 1997 yes
volvo red 1998 no
volvo blue 1998 no
saab blue 1998 yes
I like to get
volvo
red
1997
yes
1998
no
blue
1998
no
saab
blue
1998
yes
What I also like to have is Title for each column in boold.
car color year combi
volvo red 1997 yes
volvo red 1998 no
volvo blue 1998 no
saab blue 1998 yes
I like to get
volvo
red
1997
yes
1998
no
blue
1998
no
saab
blue
1998
yes
What I also like to have is Title for each column in boold.
well ok,
a sample just from head,
without your header
(don't know how to place it
->as one line or as four entries)
procedure populate_thelist(AList : TStrings; AQuery : TQuery);
const
indentspace = ' ';
var
h1,h2,h3,h4 : String;
begin
if assigned(Alist) then
begin
//some inits
AList.Clear;
h1 := '';
h2 := '';
h3 := '';
h4 := '';
//prepare query
if not AQuery.Active then
AQuery.Open;
AQuery.first;
//work
while not AQuery.Eof do
begin
if AQuery.Fields[0].AsString <> h1 then
begin
AList.Add(AQuery.Fields[0] .AsString) ;
h1 := AQuery.Fields[0].AsString;
end;
if AQuery.Fields[1].AsString <> h2 then
begin
AList.Add(IndentSpace+AQue ry.Fields[ 1].AsStrin g);
h2 := AQuery.Fields[1].AsString;
end;
if AQuery.Fields[2].AsString <> h3 then
begin
AList.Add(IndentSpace+Inde ntSpace+AQ uery.Field s[2].AsStr ing);
h3 := AQuery.Fields[2].AsString;
end;
if AQuery.Fields[3].AsString <> h4 then
begin
AList.Add(IndentSpace+Inde ntSpace+In dentSpace+ AQuery.Fie lds[3].AsS tring);
h4 := AQuery.Fields[3].AsString;
end;
AQuery.Next;
end;
end;
end;
calling sample
populate_thelist(combobox1 .items,que ry1);
not tested
meikl ;-)
a sample just from head,
without your header
(don't know how to place it
->as one line or as four entries)
procedure populate_thelist(AList : TStrings; AQuery : TQuery);
const
indentspace = ' ';
var
h1,h2,h3,h4 : String;
begin
if assigned(Alist) then
begin
//some inits
AList.Clear;
h1 := '';
h2 := '';
h3 := '';
h4 := '';
//prepare query
if not AQuery.Active then
AQuery.Open;
AQuery.first;
//work
while not AQuery.Eof do
begin
if AQuery.Fields[0].AsString <> h1 then
begin
AList.Add(AQuery.Fields[0]
h1 := AQuery.Fields[0].AsString;
end;
if AQuery.Fields[1].AsString <> h2 then
begin
AList.Add(IndentSpace+AQue
h2 := AQuery.Fields[1].AsString;
end;
if AQuery.Fields[2].AsString <> h3 then
begin
AList.Add(IndentSpace+Inde
h3 := AQuery.Fields[2].AsString;
end;
if AQuery.Fields[3].AsString <> h4 then
begin
AList.Add(IndentSpace+Inde
h4 := AQuery.Fields[3].AsString;
end;
AQuery.Next;
end;
end;
end;
calling sample
populate_thelist(combobox1
not tested
meikl ;-)
ASKER
I tried it another way by during the query using AddObject and puting the level (TObject(integer)) in the combobox.
Then on OnDrawIntem use
TCustomComboBox(Control).C anvas.Text Out(Rect.L eft+rc,Rec t.Top,TCom boBox(Cont rol).Items [Index])
Where rc depends on Integer(Combobox.Items.Obj ects[Index ])
What I also tried was to paint lines in the combobox. To have it look like a treewiev. But I don't have the code where I'm sitting right now.
(I used
TCustomComboBox(Control).C anvas.Move TO
And
TCustomComboBox(Control).L ineTo)
It looks like I have missed something because It get som unwanted lines on some places.
If someone can help me paint a nice tree I would be glad to add 200 more points.
I will try to get back with the code I have right now in the beginning of next week
Then on OnDrawIntem use
TCustomComboBox(Control).C
Where rc depends on Integer(Combobox.Items.Obj
What I also tried was to paint lines in the combobox. To have it look like a treewiev. But I don't have the code where I'm sitting right now.
(I used
TCustomComboBox(Control).C
And
TCustomComboBox(Control).L
It looks like I have missed something because It get som unwanted lines on some places.
If someone can help me paint a nice tree I would be glad to add 200 more points.
I will try to get back with the code I have right now in the beginning of next week
This is a code what i write for 2 hours:
unit AttrFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
PAttrData = ^TAttrData;
TAttrData = record
attrs: array of string[20];
end;
PAttrNode = ^TAttrNode;
TAttrNode = record
value: string[20];
level: Integer;
items: array of PAttrNode;
end;
TAttrDataArray = array of PAttrData;
PAttrDataArray = ^TAttrDataArray;
TAttrNodeArray = array of PAttrNode;
PAttrNodeArray = ^TAttrNodeArray;
TAttrTree = class(TObject)
private
FData: TAttrDataArray;
FNodes: TAttrNodeArray;
FRootNode: TAttrNode;
function NewAttrData(attrs: array of String): PAttrData;
function FindNode(ANode: PAttrNode; ACar: PAttrData): PAttrNode;
procedure MakeTree;
procedure LoadNodes(ANode: PAttrNode);
procedure LoadData; virtual;
public
destructor Destroy; override;
procedure Load;
procedure Clear;
public
property Data: TAttrDataArray read FData;
property Nodes: TAttrNodeArray read FNodes;
property RootNode: TAttrNode read FRootNode;
end;
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure ComboBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
procedure FormShow(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
AttrTree: TAttrTree;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
destructor TAttrTree.Destroy;
begin
Clear;
inherited;
end;
procedure TAttrTree.Clear;
var
i: Integer;
begin
for i:=0 to Length(FNodes)-1 do
FreeMem(FNodes[i]);
SetLength(FNodes, 0);
for i:=0 to Length(FData)-1 do
FreeMem(FData[i]);
SetLength(FData, 0);
end;
procedure TAttrTree.Load;
begin
Clear;
LoadData;
MakeTree;
LoadNodes(@FRootNode);
end;
procedure TAttrTree.LoadData;
begin
// place where the code that retriving data from the database
SetLength(FData, 5); // recordcount
FData[0] := NewAttrData(['volvo', 'red', '1997', 'yes']);
FData[1] := NewAttrData(['volvo', 'red', '1998', 'no']);
FData[2] := NewAttrData(['volvo', 'blue', '1998', 'no']);
FData[3] := NewAttrData(['saab', 'blue', '1998', 'yes']);
// You can easy add additional atributes, for example
FData[4] := NewAttrData(['saab', 'green', '1998', 'yes', 'ABS']);
end;
function TAttrTree.NewAttrData(attr s: array of String): PAttrData;
var
i: Integer;
begin
GetMem(Result, sizeof(TAttrData));
FillChar(Result^, sizeof(TAttrData), 0);
SetLength(Result^.attrs, Length(attrs));
for i:=0 to Length(attrs)-1 do
Result^.attrs[i] := attrs[i];
end;
function TAttrTree.FindNode(ANode: PAttrNode; ACar: PAttrData): PAttrNode;
var
i, Count: Integer;
begin
Result := nil;
Count := Length(ANode^.Items);
for i:=0 to Count - 1 do
if ANode^.Items[i]^.value = ACar^.attrs[ANode^.Level] then
begin
Result := ANode^.Items[i];
break;
end;
if Result = nil then
begin
Count := Count + 1;
SetLength(ANode^.Items, Count);
GetMem(Result, sizeof(TAttrNode));
FillChar(Result^, sizeof(TAttrNode), 0);
Result.level := ANode.level + 1;
Result.Value := ACar^.attrs[ANode^.Level];
ANode^.Items[Count-1] := Result;
end;
if ANode^.level < Length(ACar^.attrs)-1 then
Result := FindNode(Result, ACar);
end;
procedure TAttrTree.MakeTree;
var
i: Integer;
begin
for i:=0 to Length(FData)-1 do
FindNode(@FRootNode, FData[i]);
end;
procedure TAttrTree.LoadNodes(ANode: PAttrNode);
var
i, l, Count: Integer;
begin
Count := Length(ANode^.Items);
for i:=0 to Count-1 do
begin
l := Length(FNodes);
SetLength(FNodes, l+1);
FNodes[l] := ANode^.Items[i];
LoadNodes(FNodes[l]);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
AttrTree := TAttrTree.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
AttrTree.Free;
end;
procedure TForm1.FormShow(Sender: TObject);
var
i: Integer;
begin
AttrTree.Load;
ComboBox1.Style := csOwnerDrawFixed;
ComboBox1.Items.Clear;
for i:=0 to Length(AttrTree.Nodes)-1 do
ComboBox1.Items.Add('');
end;
procedure TForm1.ComboBox1DrawItem(C ontrol: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
Node: PAttrNode;
begin
Node := AttrTree.Nodes[Index];
with TCustomComboBox(Control).C anvas do
begin
FillRect(Rect);
if Node^.Level = 1 then
Font.Style := [fsBold] else
Font.Style := [];
TextOut(Rect.Left+10*(Node ^.Level-1) +1, Rect.Top, Node^.value);
end;
end;
end.
unit AttrFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
PAttrData = ^TAttrData;
TAttrData = record
attrs: array of string[20];
end;
PAttrNode = ^TAttrNode;
TAttrNode = record
value: string[20];
level: Integer;
items: array of PAttrNode;
end;
TAttrDataArray = array of PAttrData;
PAttrDataArray = ^TAttrDataArray;
TAttrNodeArray = array of PAttrNode;
PAttrNodeArray = ^TAttrNodeArray;
TAttrTree = class(TObject)
private
FData: TAttrDataArray;
FNodes: TAttrNodeArray;
FRootNode: TAttrNode;
function NewAttrData(attrs: array of String): PAttrData;
function FindNode(ANode: PAttrNode; ACar: PAttrData): PAttrNode;
procedure MakeTree;
procedure LoadNodes(ANode: PAttrNode);
procedure LoadData; virtual;
public
destructor Destroy; override;
procedure Load;
procedure Clear;
public
property Data: TAttrDataArray read FData;
property Nodes: TAttrNodeArray read FNodes;
property RootNode: TAttrNode read FRootNode;
end;
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure ComboBox1DrawItem(Control:
Rect: TRect; State: TOwnerDrawState);
procedure FormShow(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
AttrTree: TAttrTree;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
destructor TAttrTree.Destroy;
begin
Clear;
inherited;
end;
procedure TAttrTree.Clear;
var
i: Integer;
begin
for i:=0 to Length(FNodes)-1 do
FreeMem(FNodes[i]);
SetLength(FNodes, 0);
for i:=0 to Length(FData)-1 do
FreeMem(FData[i]);
SetLength(FData, 0);
end;
procedure TAttrTree.Load;
begin
Clear;
LoadData;
MakeTree;
LoadNodes(@FRootNode);
end;
procedure TAttrTree.LoadData;
begin
// place where the code that retriving data from the database
SetLength(FData, 5); // recordcount
FData[0] := NewAttrData(['volvo', 'red', '1997', 'yes']);
FData[1] := NewAttrData(['volvo', 'red', '1998', 'no']);
FData[2] := NewAttrData(['volvo', 'blue', '1998', 'no']);
FData[3] := NewAttrData(['saab', 'blue', '1998', 'yes']);
// You can easy add additional atributes, for example
FData[4] := NewAttrData(['saab', 'green', '1998', 'yes', 'ABS']);
end;
function TAttrTree.NewAttrData(attr
var
i: Integer;
begin
GetMem(Result, sizeof(TAttrData));
FillChar(Result^, sizeof(TAttrData), 0);
SetLength(Result^.attrs, Length(attrs));
for i:=0 to Length(attrs)-1 do
Result^.attrs[i] := attrs[i];
end;
function TAttrTree.FindNode(ANode: PAttrNode; ACar: PAttrData): PAttrNode;
var
i, Count: Integer;
begin
Result := nil;
Count := Length(ANode^.Items);
for i:=0 to Count - 1 do
if ANode^.Items[i]^.value = ACar^.attrs[ANode^.Level] then
begin
Result := ANode^.Items[i];
break;
end;
if Result = nil then
begin
Count := Count + 1;
SetLength(ANode^.Items, Count);
GetMem(Result, sizeof(TAttrNode));
FillChar(Result^, sizeof(TAttrNode), 0);
Result.level := ANode.level + 1;
Result.Value := ACar^.attrs[ANode^.Level];
ANode^.Items[Count-1] := Result;
end;
if ANode^.level < Length(ACar^.attrs)-1 then
Result := FindNode(Result, ACar);
end;
procedure TAttrTree.MakeTree;
var
i: Integer;
begin
for i:=0 to Length(FData)-1 do
FindNode(@FRootNode, FData[i]);
end;
procedure TAttrTree.LoadNodes(ANode:
var
i, l, Count: Integer;
begin
Count := Length(ANode^.Items);
for i:=0 to Count-1 do
begin
l := Length(FNodes);
SetLength(FNodes, l+1);
FNodes[l] := ANode^.Items[i];
LoadNodes(FNodes[l]);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
AttrTree := TAttrTree.Create;
end;
procedure TForm1.FormDestroy(Sender:
begin
AttrTree.Free;
end;
procedure TForm1.FormShow(Sender: TObject);
var
i: Integer;
begin
AttrTree.Load;
ComboBox1.Style := csOwnerDrawFixed;
ComboBox1.Items.Clear;
for i:=0 to Length(AttrTree.Nodes)-1 do
ComboBox1.Items.Add('');
end;
procedure TForm1.ComboBox1DrawItem(C
Rect: TRect; State: TOwnerDrawState);
var
Node: PAttrNode;
begin
Node := AttrTree.Nodes[Index];
with TCustomComboBox(Control).C
begin
FillRect(Rect);
if Node^.Level = 1 then
Font.Style := [fsBold] else
Font.Style := [];
TextOut(Rect.Left+10*(Node
end;
end;
end.
Hi
I want to add a couple of KBs of source too :) and some explanations:
- drop combobox on your form
- set it's style to csOwnerDrawFixed
- fill it with rows from your query (as is, just separate field values with some delimiter character - space, for example)
- make some initialization (see Form's OnCreate handler)
and here is code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure ComboBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
Indent: Integer;
Levels: Integer;
prevlevel: TStringList;
separatorstr: string;
public
{ Public declarations }
procedure FillComboItems(Combo: TCustomComboBox);
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
begin
// we do our initialization here
Indent := 8; // default Indent
Levels := 4; // Number of levels
prevlevel := TStringList.Create; // Storage for level's values
for i := 0 to Levels - 1 do // and we initialize them
prevlevel.Add(''); // with empty strings
separatorstr := ' '; // separator between columns of initial combobox content
FillComboItems(ComboBox1); // Refill Comobox
end;
procedure TForm1.ComboBox1DrawItem(C ontrol: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var tmp: string;
begin
tmp := (Control as TCustomCombobox).Items[Ind ex];
with (Control as TCustomCombobox) do
begin
Canvas.Font := Font;
Canvas.Brush := Brush;
if (Index >= 0) and (odSelected in State) then
begin
Canvas.Brush.Color := clHighlight;
Canvas.Font.Color := clHighlightText;
end;
Canvas.FillRect(Rect);
Rect.Left := Rect.Left +
Integer(Pointer((Control as TCustomCombobox).Items.Obj ects[Index ])) *
Indent;
Canvas.TextOut(Rect.Left,R ect.Top,tm p);
end;
end;
procedure TForm1.FillComboItems(Comb o: TCustomComboBox);
function GetPartOfItem(fullstr: string; partindex: Integer): string;
var st,i: integer;
begin
st := 1;
for i := 0 to partindex - 1 do
begin
st := AnsiPos(separatorstr,fulls tr);
Delete(fullstr,1,st);
end;
st := AnsiPos(separatorstr,fulls tr);
if st <> 0 then
Delete(fullstr,st,Length(f ullstr));
Result := fullstr;
end;
procedure EmptyRestOfPrevVals(prevLe vel: TStrings; Index: Integer);
var i: Integer;
begin
for i := Index + 1 to prevLevel.Count - 1 do
prevLevel[i] := '';
end;
var tmp: TStringList;
str, part: string;
i,j: Integer;
begin
tmp := TStringList.Create;
tmp.Assign(Combo.Items);
Combo.Items.Clear;
for i := 0 to tmp.Count - 1 do
begin
str := tmp[i];
for j := 0 to Levels - 1 do
begin
part := GetPartOfItem(str, j);
if AnsiCompareText(prevLevel[ j], part) = 0 then
Continue
else begin
prevLevel[j] := part;
EmptyRestOfPrevVals(prevLe vel,j);
Combo.Items.AddObject(part ,Pointer(j ));
end;
end;
end;
tmp.Free;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
prevlevel.Free;
end;
end.
Hope this will help
I want to add a couple of KBs of source too :) and some explanations:
- drop combobox on your form
- set it's style to csOwnerDrawFixed
- fill it with rows from your query (as is, just separate field values with some delimiter character - space, for example)
- make some initialization (see Form's OnCreate handler)
and here is code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure ComboBox1DrawItem(Control:
Rect: TRect; State: TOwnerDrawState);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
Indent: Integer;
Levels: Integer;
prevlevel: TStringList;
separatorstr: string;
public
{ Public declarations }
procedure FillComboItems(Combo: TCustomComboBox);
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var i: Integer;
begin
// we do our initialization here
Indent := 8; // default Indent
Levels := 4; // Number of levels
prevlevel := TStringList.Create; // Storage for level's values
for i := 0 to Levels - 1 do // and we initialize them
prevlevel.Add(''); // with empty strings
separatorstr := ' '; // separator between columns of initial combobox content
FillComboItems(ComboBox1);
end;
procedure TForm1.ComboBox1DrawItem(C
Rect: TRect; State: TOwnerDrawState);
var tmp: string;
begin
tmp := (Control as TCustomCombobox).Items[Ind
with (Control as TCustomCombobox) do
begin
Canvas.Font := Font;
Canvas.Brush := Brush;
if (Index >= 0) and (odSelected in State) then
begin
Canvas.Brush.Color := clHighlight;
Canvas.Font.Color := clHighlightText;
end;
Canvas.FillRect(Rect);
Rect.Left := Rect.Left +
Integer(Pointer((Control as TCustomCombobox).Items.Obj
Indent;
Canvas.TextOut(Rect.Left,R
end;
end;
procedure TForm1.FillComboItems(Comb
function GetPartOfItem(fullstr: string; partindex: Integer): string;
var st,i: integer;
begin
st := 1;
for i := 0 to partindex - 1 do
begin
st := AnsiPos(separatorstr,fulls
Delete(fullstr,1,st);
end;
st := AnsiPos(separatorstr,fulls
if st <> 0 then
Delete(fullstr,st,Length(f
Result := fullstr;
end;
procedure EmptyRestOfPrevVals(prevLe
var i: Integer;
begin
for i := Index + 1 to prevLevel.Count - 1 do
prevLevel[i] := '';
end;
var tmp: TStringList;
str, part: string;
i,j: Integer;
begin
tmp := TStringList.Create;
tmp.Assign(Combo.Items);
Combo.Items.Clear;
for i := 0 to tmp.Count - 1 do
begin
str := tmp[i];
for j := 0 to Levels - 1 do
begin
part := GetPartOfItem(str, j);
if AnsiCompareText(prevLevel[
Continue
else begin
prevLevel[j] := part;
EmptyRestOfPrevVals(prevLe
Combo.Items.AddObject(part
end;
end;
end;
tmp.Free;
end;
procedure TForm1.FormDestroy(Sender:
begin
prevlevel.Free;
end;
end.
Hope this will help
The code that i wrote is easy to modify and scale.
It can be used for others purposes.
Number of fields is unlimited.
Prepared data can be used in others components,
such as TTreeView.
It can be used for others purposes.
Number of fields is unlimited.
Prepared data can be used in others components,
such as TTreeView.
ASKER
Here comes the code I have right now.
Sometimes for some level the painting does not look ok.
Can someone see what I have missed?
procedure TQuoteForm.cbTestDrawItem( Control: TWinControl;
Index: Integer; Rect: TRect; State: TOwnerDrawState);
var
ix,jx, CurrentLevel, LastLevel : Integer;
DrawLevel : array [1..3] of Boolean;
begin
TCustomComboBox(Control).C anvas.Fill Rect(Rect) ;
if TTestItem(cbTest.Items.Obj ects[index ]).level = 1 then
rc:= 0
else if TTestItem(cbTest.Items.Obj ects[index ]).Level = 2 then
rc:= 18
else if TTestItem(cbTest.Items.Obj ects[index ]).Level = 3 then
rc:= 36
else if TTestItem(cbTest.Items.Obj ects[index ]).Level = 4 then
rc:= 54;
for jx := 1 to 3 do
DrawLevel[jx]:= False;
jx := Index+1;
LastLevel:= TTestsItem(cbTest.Items.Ob jects[inde x]).Level;
while (jx < cbTest.Items.Count) and (TTestsItem(cbTest.Items.O bjects[jx] ).Level >= 2) do
begin
CurrentLevel:= TTestsItem(cbTest.Items.Ob jects[jx]) .Level;
if CurrentLevel > 1 then
DrawLevel[CurrentLevel-1]: = True;
inc(jx);
if CurrentLevel= 2 then
Break;
end;
for ix := 1 to rc div 18 do
begin
if DrawLevel[ix] then
begin
TCustomComboBox(Control).C anvas.Move To((ix-1)* 18+10,Rect .Top);
TCustomComboBox(Control).C anvas.Line To((ix-1)* 18+10,rect .Bottom);
end;
end;
if rc <> 0 then
begin
TCustomComboBox(Control).C anvas.Move To(Rect.Le ft+rc-8,Re ct.Top+8);
TCustomComboBox(Control).C anvas.Line To(Rect.Le ft+rc,Rect .Top+8);
TCustomComboBox(Control).C anvas.Move To(Rect.Le ft+rc-8,Re ct.Top);
TCustomComboBox(Control).C anvas.Line To(Rect.Le ft+rc-8,Re ct.Top+8);
end;
TCustomComboBox(Control).C anvas.Text Out(Rect.L eft+rc,Rec t.Top,TCom boBox(Cont rol).Items [Index]);
end;
Sometimes for some level the painting does not look ok.
Can someone see what I have missed?
procedure TQuoteForm.cbTestDrawItem(
Index: Integer; Rect: TRect; State: TOwnerDrawState);
var
ix,jx, CurrentLevel, LastLevel : Integer;
DrawLevel : array [1..3] of Boolean;
begin
TCustomComboBox(Control).C
if TTestItem(cbTest.Items.Obj
rc:= 0
else if TTestItem(cbTest.Items.Obj
rc:= 18
else if TTestItem(cbTest.Items.Obj
rc:= 36
else if TTestItem(cbTest.Items.Obj
rc:= 54;
for jx := 1 to 3 do
DrawLevel[jx]:= False;
jx := Index+1;
LastLevel:= TTestsItem(cbTest.Items.Ob
while (jx < cbTest.Items.Count) and (TTestsItem(cbTest.Items.O
begin
CurrentLevel:= TTestsItem(cbTest.Items.Ob
if CurrentLevel > 1 then
DrawLevel[CurrentLevel-1]:
inc(jx);
if CurrentLevel= 2 then
Break;
end;
for ix := 1 to rc div 18 do
begin
if DrawLevel[ix] then
begin
TCustomComboBox(Control).C
TCustomComboBox(Control).C
end;
end;
if rc <> 0 then
begin
TCustomComboBox(Control).C
TCustomComboBox(Control).C
TCustomComboBox(Control).C
TCustomComboBox(Control).C
end;
TCustomComboBox(Control).C
end;
procedure TForm1.ComboBox1DrawItem(C ontrol: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
i, j: Integer;
Level: Integer;
Levels: array [1..4] of Boolean;
begin
with TCustomComboBox(Control).C anvas do
begin
FillRect(Rect);
Level := cbTest.Items.Objects[Index ]).Level;
for i:=1 to 4 do Levels[i] := False;
for i:=Index+1 to cbTest.Items.Count-1 do
if TTestsItem(cbTest.Items.Ob jects[i]). Level >= Level then
Levels[TTestsItem(cbTest.I tems.Objec ts[i]).Lev el] := True
else
begin
for j:=1 to TTestsItem(cbTest.Items.Ob jects[i]). Level do Levels[j] := True;
break;
end;
Levels[2] := Levels[2] or Levels[1];
for i:=1 to Level - 1 do
if Levels[i+1] then
begin
MoveTo(Rect.Left+(i-1)*18+ 10, Rect.Top);
LineTo(Rect.Left+(i-1)*18+ 10, Rect.Bottom);
end;
if Level > 1 then
begin
MoveTo(Rect.Left+(Level-2) *18+10, Rect.Top);
LineTo(Rect.Left+(Level-2) *18+10, Rect.Bottom + (Rect.Top - Rect.Bottom) div 2);
LineTo(Rect.Left+(Level-2) *18+10+17, Rect.Bottom + (Rect.Top - Rect.Bottom) div 2);
if Levels[Level] then
begin
MoveTo(Rect.Left+(Level-2) *18+10, Rect.Top);
LineTo(Rect.Left+(Level-2) *18+10, Rect.Bottom);
end;
end;
TextOut(Rect.Left+18*(Leve l-1), Rect.Top, Node.value);
end;
end;
Rect: TRect; State: TOwnerDrawState);
var
i, j: Integer;
Level: Integer;
Levels: array [1..4] of Boolean;
begin
with TCustomComboBox(Control).C
begin
FillRect(Rect);
Level := cbTest.Items.Objects[Index
for i:=1 to 4 do Levels[i] := False;
for i:=Index+1 to cbTest.Items.Count-1 do
if TTestsItem(cbTest.Items.Ob
Levels[TTestsItem(cbTest.I
else
begin
for j:=1 to TTestsItem(cbTest.Items.Ob
break;
end;
Levels[2] := Levels[2] or Levels[1];
for i:=1 to Level - 1 do
if Levels[i+1] then
begin
MoveTo(Rect.Left+(i-1)*18+
LineTo(Rect.Left+(i-1)*18+
end;
if Level > 1 then
begin
MoveTo(Rect.Left+(Level-2)
LineTo(Rect.Left+(Level-2)
LineTo(Rect.Left+(Level-2)
if Levels[Level] then
begin
MoveTo(Rect.Left+(Level-2)
LineTo(Rect.Left+(Level-2)
end;
end;
TextOut(Rect.Left+18*(Leve
end;
end;
ASKER
where do you get Node.Value in
TextOut(Rect.Left+18*(Leve l-1), Rect.Top, Node.value);
TextOut(Rect.Left+18*(Leve
I test this proc with my data...and forgort to replace this text. Replace this line with:
TCustomComboBox(Control).C anvas.Text Out(Rect.L eft+rc,Rec t.Top,TCom boBox(Cont rol).Items [Index]);
TCustomComboBox(Control).C
Do you see the TAttrTree object in my comment above?
ASKER
Sorry That is should be :
TextOut(Rect.Left+18*(Leve l-1), Rect.Top, TComboBox(Control).Items[I ndex]);
But it still don't looks complete ok.
The line vertical line for the keeps going where it not should. (for the first level)
for example
aaaaaaaaa
|_ bbbbbbbbb
| |_cccccccc
| |_ ddddddddd
The first vertical line is always there......
TextOut(Rect.Left+18*(Leve
But it still don't looks complete ok.
The line vertical line for the keeps going where it not should. (for the first level)
for example
aaaaaaaaa
|_ bbbbbbbbb
| |_cccccccc
| |_ ddddddddd
The first vertical line is always there......
This proc work fine in my project with my data.
Check your data for correct Level value.
Check your data for correct Level value.
Please, chek this code on your computer
unit AttrFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
PAttrData = ^TAttrData;
TAttrData = record
attrs: array of string[20];
end;
PAttrNode = ^TAttrNode;
TAttrNode = record
value: string[20];
level: Integer;
items: array of PAttrNode;
end;
TAttrDataArray = array of PAttrData;
PAttrDataArray = ^TAttrDataArray;
TAttrNodeArray = array of PAttrNode;
PAttrNodeArray = ^TAttrNodeArray;
TAttrTree = class(TObject)
private
FData: TAttrDataArray;
FNodes: TAttrNodeArray;
FRootNode: TAttrNode;
function NewAttrData(attrs: array of String): PAttrData;
function FindNode(ANode: PAttrNode; ACar: PAttrData): PAttrNode;
procedure MakeTree;
procedure LoadNodes(ANode: PAttrNode);
procedure LoadData; virtual;
public
destructor Destroy; override;
procedure Load;
procedure Clear;
public
property Data: TAttrDataArray read FData;
property Nodes: TAttrNodeArray read FNodes;
property RootNode: TAttrNode read FRootNode;
end;
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure ComboBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
procedure FormShow(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
AttrTree: TAttrTree;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
destructor TAttrTree.Destroy;
begin
Clear;
inherited;
end;
procedure TAttrTree.Clear;
var
i: Integer;
begin
for i:=0 to Length(FNodes)-1 do
FreeMem(FNodes[i]);
SetLength(FNodes, 0);
for i:=0 to Length(FData)-1 do
FreeMem(FData[i]);
SetLength(FData, 0);
end;
procedure TAttrTree.Load;
begin
Clear;
LoadData;
MakeTree;
LoadNodes(@FRootNode);
end;
procedure TAttrTree.LoadData;
begin
// place where the code that retriving data from the database
SetLength(FData, 4); // recordcount
FData[0] := NewAttrData(['volvo', 'red', '1997', 'yes']);
FData[1] := NewAttrData(['volvo', 'red', '1998', 'no']);
FData[2] := NewAttrData(['volvo', 'blue', '1998', 'no']);
FData[3] := NewAttrData(['saab', 'blue', '1998', 'yes']);
// You can easy add additional atributes, for example
// FData[4] := NewAttrData(['saab', 'green', '1998', 'yes', 'ABS']);
end;
function TAttrTree.NewAttrData(attr s: array of String): PAttrData;
var
i: Integer;
begin
GetMem(Result, sizeof(TAttrData));
FillChar(Result^, sizeof(TAttrData), 0);
SetLength(Result^.attrs, Length(attrs));
for i:=0 to Length(attrs)-1 do
Result^.attrs[i] := attrs[i];
end;
function TAttrTree.FindNode(ANode: PAttrNode; ACar: PAttrData): PAttrNode;
var
i, Count: Integer;
begin
Result := nil;
Count := Length(ANode^.Items);
for i:=0 to Count - 1 do
if ANode^.Items[i]^.value = ACar^.attrs[ANode^.Level] then
begin
Result := ANode^.Items[i];
break;
end;
if Result = nil then
begin
Count := Count + 1;
SetLength(ANode^.Items, Count);
GetMem(Result, sizeof(TAttrNode));
FillChar(Result^, sizeof(TAttrNode), 0);
Result.level := ANode.level + 1;
Result.Value := ACar^.attrs[ANode^.Level];
ANode^.Items[Count-1] := Result;
end;
if ANode^.level < Length(ACar^.attrs)-1 then
Result := FindNode(Result, ACar);
end;
procedure TAttrTree.MakeTree;
var
i: Integer;
begin
for i:=0 to Length(FData)-1 do
FindNode(@FRootNode, FData[i]);
end;
procedure TAttrTree.LoadNodes(ANode: PAttrNode);
var
i, l, Count: Integer;
begin
Count := Length(ANode^.Items);
for i:=0 to Count-1 do
begin
l := Length(FNodes);
SetLength(FNodes, l+1);
FNodes[l] := ANode^.Items[i];
LoadNodes(FNodes[l]);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
AttrTree := TAttrTree.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
AttrTree.Free;
end;
procedure TForm1.FormShow(Sender: TObject);
var
i: Integer;
begin
AttrTree.Load;
ComboBox1.Style := csOwnerDrawFixed;
ComboBox1.Items.Clear;
for i:=0 to Length(AttrTree.Nodes)-1 do
ComboBox1.Items.Add('');
end;
end.
unit AttrFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
PAttrData = ^TAttrData;
TAttrData = record
attrs: array of string[20];
end;
PAttrNode = ^TAttrNode;
TAttrNode = record
value: string[20];
level: Integer;
items: array of PAttrNode;
end;
TAttrDataArray = array of PAttrData;
PAttrDataArray = ^TAttrDataArray;
TAttrNodeArray = array of PAttrNode;
PAttrNodeArray = ^TAttrNodeArray;
TAttrTree = class(TObject)
private
FData: TAttrDataArray;
FNodes: TAttrNodeArray;
FRootNode: TAttrNode;
function NewAttrData(attrs: array of String): PAttrData;
function FindNode(ANode: PAttrNode; ACar: PAttrData): PAttrNode;
procedure MakeTree;
procedure LoadNodes(ANode: PAttrNode);
procedure LoadData; virtual;
public
destructor Destroy; override;
procedure Load;
procedure Clear;
public
property Data: TAttrDataArray read FData;
property Nodes: TAttrNodeArray read FNodes;
property RootNode: TAttrNode read FRootNode;
end;
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure ComboBox1DrawItem(Control:
Rect: TRect; State: TOwnerDrawState);
procedure FormShow(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
AttrTree: TAttrTree;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
destructor TAttrTree.Destroy;
begin
Clear;
inherited;
end;
procedure TAttrTree.Clear;
var
i: Integer;
begin
for i:=0 to Length(FNodes)-1 do
FreeMem(FNodes[i]);
SetLength(FNodes, 0);
for i:=0 to Length(FData)-1 do
FreeMem(FData[i]);
SetLength(FData, 0);
end;
procedure TAttrTree.Load;
begin
Clear;
LoadData;
MakeTree;
LoadNodes(@FRootNode);
end;
procedure TAttrTree.LoadData;
begin
// place where the code that retriving data from the database
SetLength(FData, 4); // recordcount
FData[0] := NewAttrData(['volvo', 'red', '1997', 'yes']);
FData[1] := NewAttrData(['volvo', 'red', '1998', 'no']);
FData[2] := NewAttrData(['volvo', 'blue', '1998', 'no']);
FData[3] := NewAttrData(['saab', 'blue', '1998', 'yes']);
// You can easy add additional atributes, for example
// FData[4] := NewAttrData(['saab', 'green', '1998', 'yes', 'ABS']);
end;
function TAttrTree.NewAttrData(attr
var
i: Integer;
begin
GetMem(Result, sizeof(TAttrData));
FillChar(Result^, sizeof(TAttrData), 0);
SetLength(Result^.attrs, Length(attrs));
for i:=0 to Length(attrs)-1 do
Result^.attrs[i] := attrs[i];
end;
function TAttrTree.FindNode(ANode: PAttrNode; ACar: PAttrData): PAttrNode;
var
i, Count: Integer;
begin
Result := nil;
Count := Length(ANode^.Items);
for i:=0 to Count - 1 do
if ANode^.Items[i]^.value = ACar^.attrs[ANode^.Level] then
begin
Result := ANode^.Items[i];
break;
end;
if Result = nil then
begin
Count := Count + 1;
SetLength(ANode^.Items, Count);
GetMem(Result, sizeof(TAttrNode));
FillChar(Result^, sizeof(TAttrNode), 0);
Result.level := ANode.level + 1;
Result.Value := ACar^.attrs[ANode^.Level];
ANode^.Items[Count-1] := Result;
end;
if ANode^.level < Length(ACar^.attrs)-1 then
Result := FindNode(Result, ACar);
end;
procedure TAttrTree.MakeTree;
var
i: Integer;
begin
for i:=0 to Length(FData)-1 do
FindNode(@FRootNode, FData[i]);
end;
procedure TAttrTree.LoadNodes(ANode:
var
i, l, Count: Integer;
begin
Count := Length(ANode^.Items);
for i:=0 to Count-1 do
begin
l := Length(FNodes);
SetLength(FNodes, l+1);
FNodes[l] := ANode^.Items[i];
LoadNodes(FNodes[l]);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
AttrTree := TAttrTree.Create;
end;
procedure TForm1.FormDestroy(Sender:
begin
AttrTree.Free;
end;
procedure TForm1.FormShow(Sender: TObject);
var
i: Integer;
begin
AttrTree.Load;
ComboBox1.Style := csOwnerDrawFixed;
ComboBox1.Items.Clear;
for i:=0 to Length(AttrTree.Nodes)-1 do
ComboBox1.Items.Add('');
end;
end.
ASKER
My levels are ok. I added them to the output with
TextOut(Rect.Left+18*(Leve l-1), Rect.Top, TComboBox(Control).Items[I ndex] + ' ' + IntToStr(TTestItem(cbTest. Items.Obje cts[Index] ).Level));
To check them.
It is not all the time the vertical line is there.
If I know how to convert a Tcontrolcanvas to a tbitmap canvas I could send you a bitmap with my result so you could se it.
TextOut(Rect.Left+18*(Leve
To check them.
It is not all the time the vertical line is there.
If I know how to convert a Tcontrolcanvas to a tbitmap canvas I could send you a bitmap with my result so you could se it.
I find some error, will trying to correct it.
This is a correct code, use it:
procedure TForm1.ComboBox1DrawItem(C ontrol: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
i, j, Level, ItemLevel: Integer;
Levels: array [1..4] of Boolean;
S: String;
IsLastNode, Flag: Boolean;
begin
ItemLevel := TTestsItem(cbTest.Items.Ob jects[Inde x]).Level;
with TCustomComboBox(Control).C anvas do
begin
FillRect(Rect);
for i:=1 to 4 do Levels[i] := False;
IsLastNode := True;
for i:=Index+1 to cbTest.Items.Count-1 do
begin
Level := TTestsItem(cbTest.Items.Ob jects[i]). level;
Flag := False;
for j:=1 to Level-1 do Flag := Flag or Levels[j];
if not Flag then Levels[Level] := True;
if Level = ItemLevel then
if not Flag then IsLastNode := False;
end;
Levels[2] := Levels[2] or Levels[1];
for i:=1 to ItemLevel-1 do
if Levels[i+1] then
begin
MoveTo(Rect.Left+(i-1)*18+ 10, Rect.Top);
LineTo(Rect.Left+(i-1)*18+ 10, Rect.Bottom);
end;
if ItemLevel > 1 then
begin
MoveTo(Rect.Left+(ItemLeve l-2)*18+10 , Rect.Top);
LineTo(Rect.Left+(ItemLeve l-2)*18+10 , Rect.Bottom + (Rect.Top - Rect.Bottom) div 2);
LineTo(Rect.Left+(ItemLeve l-2)*18+10 +17, Rect.Bottom + (Rect.Top - Rect.Bottom) div 2);
if not IsLastNode then
begin
MoveTo(Rect.Left+(ItemLeve l-2)*18+10 , Rect.Top);
LineTo(Rect.Left+(ItemLeve l-2)*18+10 , Rect.Bottom);
end;
end;
TextOut(Rect.Left+18*(Item Level-1), Rect.Top, cbTest.Items[i]);
end;
end;
procedure TForm1.ComboBox1DrawItem(C
Rect: TRect; State: TOwnerDrawState);
var
i, j, Level, ItemLevel: Integer;
Levels: array [1..4] of Boolean;
S: String;
IsLastNode, Flag: Boolean;
begin
ItemLevel := TTestsItem(cbTest.Items.Ob
with TCustomComboBox(Control).C
begin
FillRect(Rect);
for i:=1 to 4 do Levels[i] := False;
IsLastNode := True;
for i:=Index+1 to cbTest.Items.Count-1 do
begin
Level := TTestsItem(cbTest.Items.Ob
Flag := False;
for j:=1 to Level-1 do Flag := Flag or Levels[j];
if not Flag then Levels[Level] := True;
if Level = ItemLevel then
if not Flag then IsLastNode := False;
end;
Levels[2] := Levels[2] or Levels[1];
for i:=1 to ItemLevel-1 do
if Levels[i+1] then
begin
MoveTo(Rect.Left+(i-1)*18+
LineTo(Rect.Left+(i-1)*18+
end;
if ItemLevel > 1 then
begin
MoveTo(Rect.Left+(ItemLeve
LineTo(Rect.Left+(ItemLeve
LineTo(Rect.Left+(ItemLeve
if not IsLastNode then
begin
MoveTo(Rect.Left+(ItemLeve
LineTo(Rect.Left+(ItemLeve
end;
end;
TextOut(Rect.Left+18*(Item
end;
end;
ASKER
TextOut(Rect.Left+18*(Item Level-1), Rect.Top, cbTest.Items[i]);
should be
TextOut(Rect.Left+18*(Item Level-1), Rect.Top, cbTest.Items[Index]);
otherwise it writes the same text all the time. Right?
should be
TextOut(Rect.Left+18*(Item
otherwise it writes the same text all the time. Right?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
It works wounderfoul.... Thanks!
(i guess there are some free components for that)
listening . . .