I'm need to draw some graphics as node image.
Like it draws images from ImageList in OnGetImageIndex event, but from the single source like TIcon, TImage, TBitmap.
In my situation all nodes have it own icons and places in UserData record.
How I can draw theese icons to nodes?
I found this code here, and tried to adept it for my situation:
procedure TForm10.Button1Click(Sender: TObject);
var
Node: PVirtualNode;
begin
VirtualStringTree1.AddChild(nil);
Node := VirtualStringTree1.AddChild(nil);
VirtualStringTree1.AddChild(Node);
Node := VirtualStringTree1.AddChild(Node);
VirtualStringTree1.AddChild(Node);
VirtualStringTree1.AddChild(Node);
VirtualStringTree1.AddChild(Node);
end;
procedure TForm10.VirtualStringTree1AfterItemPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; ItemRect: TRect);
var
rImage: TRect;
OffsetLeft: Integer;
Icon: TIcon;
begin
rImage := ItemRect;
Icon := TIcon.Create;
Icon.LoadFromFile('TestIcon_16.ico');
with TVirtualStringTree(Sender) do
begin
if (toShowRoot in TreeOptions.PaintOptions) then
OffsetLeft := Indent * (GetNodeLevel(Node) + 1)
else
OffsetLeft := Indent * GetNodeLevel(Node);
Inc(rImage.Left, Margin + OffsetLeft);
Inc(rImage.Top, (NodeHeight[Node] - Icon.Height) div 2);
rImage.Right := rImage.Left + Icon.Width;
rImage.Bottom := rImage.Top + Icon.Height;
end;
DrawIcon(TargetCanvas.Handle, rImage.Left, rImage.Top, Icon.Handle);
end;
After button click, I see that:
Why that's happens?
Icon size 100% - 16 x 16 px.
Where I can solve problem with drawning of text?
What I do wrong?
Unfortunately VT relies on image lists not allowing using separate images. In the same time, image lists are invconvenient as long as item insert and deletion is concerned. So as a workaround option you could create an image list for each image and return it to VT via OnGetImageEx event handler.
Alternatively, you can create one dummy image list with one empty and transparent image so that VT would know what dimensions the image has and paint your own custom images in AfterPaint.
Related
I created an application that goes out and scans every computer and populates a TreeView with Hardware, Software and updates/hotfixes information:
The problem I’m having is with printing, how do you automatically expand the treeview and sends the results of the selected computer to the printer? The method I am currently using involves sending the contents to a canvas (BMP) and then send it to the printer but that does not capture the whole treeview only whatever is being displayed on the screen. Any advice? Thank you so much.
The problem with printing the TTreeView is that the part that isn't visible has nothing to be drawn. (Windows draws only the visible portion of the control, so when you use PrintTo or the API PrintWindow function, it only has the visible nodes available to print - the non-displayed content hasn't yet been drawn and therefore can't be printed.)
If a tabular layout works (no lines, just indented levels), the easiest way is to create text and put it in a hidden TRichEdit, and then let the TRichEdit.Print handle the output. Here's an example:
// File->New->VCL Forms Application, then
// Drop a TTreeView and a TButton on the form.
// Add the following for the FormCreate (to create the treeview content)
// and button click handlers, and the following procedure to create
// the text content:
procedure TreeToText(const Tree: TTreeView; const RichEdit: TRichEdit);
var
Node: TTreeNode;
Indent: Integer;
Padding: string;
const
LevelIndent = 4;
begin
RichEdit.Clear;
Node := Tree.Items.GetFirstNode;
while Node <> nil do
begin
Padding := StringOfChar(#32, Node.Level * LevelIndent);
RichEdit.Lines.Add(Padding + Node.Text);
Node := Node.GetNext;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
HideForm: TForm;
HideEdit: TRichEdit;
begin
HideForm := TForm.Create(nil);
try
HideEdit := TRichEdit.Create(HideForm);
HideEdit.Parent := HideForm;
TreeToText(TreeView1, HideEdit);
HideEdit.Print('Printed TreeView Text');
finally
HideForm.Free;
end;
end;
procedure TForm3.FormCreate(Sender: TObject);
var
i, j: Integer;
RootNode, ChildNode: TTreeNode;
begin
RootNode := TreeView1.Items.AddChild(nil, 'Root');
for i := 1 to 6 do
begin
ChildNode := TreeView1.Items.AddChild(RootNode, Format('Root node %d', [i]));
for j := 1 to 4 do
TreeView1.Items.AddChild(ChildNode, Format('Child node %d', [j]));
end;
end;
I have a game program that requires the user to select from 50+ images to place on a 9x9 game board grid with a timage in each position. For the user to place the images, I am providing a tstringgrid which displays various images from a timagelist. The actual images are graphic symbols created in .png format with transparent regions to allow the background color of the image's parent to show through when displayed. The image selected from the tstringgrid displays correctly on the game board timage components, but not in the 'tstringgrid'. The tstringgrid displays an image's transparent areas as black which is unsightly and makes many of the symbols unreadable.
I have used the following code to load the tstringgrid:
procedure TImageForm.FormCreate(Sender: TObject);
var
r, c, n : Integer;
img:TImage;
begin
//assign a value to each cell to connect with imagelist.
//see StringGrid1DrawCell
img := timage.Create(nil);
try
n := -1;
with StringGrid1 do begin
for r := 0 to RowCount - 1 do begin
for c:= 0 to ColCount - 1 do begin
inc(n);
Cells[c,r] := IntToStr(n);
ImageList1.GetBitmap(n, img.Picture.Bitmap);
// ImageList1.AddMasked(Img.Picture.Bitmap, clBlack);
end;
end;
end;
finally
img.Free;
end;
end;
What I need to do is revise the bitmap retrieved from the list before it is displayed.
I am attempting to do this as follows:
procedure TForm1.FillBkgd (bmp : tbitmap;clr : tcolor);
//which is the imagelist index for the image
var
bmp1 : tbitmap;
begin
bmp1 := tbitmap.create;
try
bmp1.Width := 50;
bmp1.Height := 50;
with Bmp1.Canvas do begin
Brush.Color := clr; //stringgrid1.color;
Brush.Style := bsSolid;
FillRect(rect(0,0,50, 50));
end;
bmp1.Canvas.Draw(0,0, bmp);
bmp:= bmp1;
finally
bmp1.free;
end;
end;
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
{https://www.youtube.com/watch?v=LOEP312NzsE}
var
bmp : tbitmap;
s : string;
aCanvas: TCanvas;
n : integer;
begin
inherited;
if not Assigned (Imagelist1) then //or (Arow = 0) or (aCol in [0,5])
exit;
bmp := tbitmap.create;
try
s := (Sender as TStringGrid).Cells [aCol, ARow];
// Draw ImageX.Picture.Bitmap in all Rows in Col 1
aCanvas := (Sender as TStringGrid).Canvas;
// Clear current cell rect
aCanvas.FillRect(Rect);
// Draw the image in the cell
n := strtoInt (s);
ImageList1.GetBitmap(n, Bmp);
FillBkgd (bmp,clRed); //stringgrid.color
aCanvas.StretchDraw(Rect,bmp);
finally
end;
end;
My attempts to combine the symbol image with a colored background before placing it in the stringgrid have failed. It is unclear to me whether I am failing to create a solid colored bitmap or am not successfully joining the image to the background.
Function FillBkgd has big problems with the quality of the code.
bmp:= bmp1;
finally
bmp1.free;
end;
All object variables are pointers. bmp and bmp1 objects point to one area of memory that you are freeing. This leads to Access Violation. You are lucky that the pointer is not returned from the function. Function FillBkgd does not work. To get the result, you could use bmp.Assign(bmp1);.
I see a lot of redrawing the picture of ImageList (draw to Bmp, draw to Bmp1, draw ACanvas). After the first transformation transparency information is lost. Therefore at this moment it is necessary to change the background color.
s := (Sender as TStringGrid).Cells [aCol, ARow];
// Draw ImageX.Picture.Bitmap in all Rows in Col 1
aCanvas := (Sender as TStringGrid).Canvas;
// Clear current cell rect
aCanvas.FillRect(Rect);
// Draw the image in the cell
n := strtoInt (s);
//new lines
bmp.Canvas.Brush.Color := clRed;
bmp.Canvas.FillRect(TRect.Create(0, 0, ImageList1.Width, ImageList1.Height));
//new lines
ImageList1.GetBitmap(n, Bmp);
//deleted lines
//FillBkgd (bmp,clRed); //stringgrid.color
aCanvas.StretchDraw(Rect,bmp);
And do not forget to fill out finally to free bmp.
I need a control which can display thumbnails, for this I thought TListView with the ViewStyle set as vsIcon would be good enough for my purposes, unfortunately I realised that TImageList only supports images up to 256x256 in size. I know there are 3rd party solutions for this but I had hoped to work with the standard TListView.
The images I need to display are approximately 348x480 so I cannot add them to a imagelist and assign it to a listview.
So then I thought maybe I could store my images in a TList and then ownerdraw the listview, it is quite simple really just by using the CustomDrawItem method and working with the Item.DisplayRect to know exactly where to draw to, something like this (quick example):
procedure TForm1.ListView1CustomDrawItem(Sender: TCustomListView;
Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
var
ItemRect: TRect;
IconRect: TRect;
CaptionRect: TRect;
begin
DefaultDraw := False;
ItemRect := Item.DisplayRect(drBounds);
IconRect := Item.DisplayRect(drIcon);
CaptionRect := Item.DisplayRect(drLabel);
with ListView1 do
begin
if cdsHot in State then
begin
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clSkyBlue;
Canvas.FillRect(ItemRect);
end;
if cdsSelected in State then
begin
Canvas.Brush.Style := bsSolid;
Canvas.Brush.Color := clBlue;
Canvas.FillRect(ItemRect);
end;
{ my picture list is a custom control that holds bitmaps in a TList }
if MyPictureList1.Items.Count > 0 then
MyPictureList1.Draw(Canvas, IconRect.Left + 348 div 4, IconRect.Top + 2, Item.ImageIndex);
// commented out old code drawing from imagelist
{ if LargeImages <> nil then
begin
LargeImages.Draw(Canvas, IconRect.Left + LargeImages.Width div 4, 2, Item.ImageIndex);
end; }
// draw text etc
end;
end;
The problem is how to change the size of each listview item? Typically setting the imagelist will change the size of the items, but I cannot use an imagelist because of the size limitations.
I had tried ListView_SetIconSpacing(ListView1.Handle, 348, 480); which did not seem to do anything, I also tried inflating the local rects I assigned but no luck there.
Is it possible to manually set the icon/item size of a listview to be greater than 256px and if so, how could I achieve this?
How can I customize my listview to display different background colors like in the picture below ?
My listview is bound to a datasource (Livebindng). I want to use the color field to set my backgroud color.
I've customized my view this way :
3 Text items (Designation,Date and Resume)
1 Bitmap item (Couleur)
Text items are bound to datasource but there is no way to bind my Bitmap to my "color" field.
I've filled the listview ActivesUpdateObjects event but this is not enough as bitmap is not changed when datasource record is updated!
procedure TfrmMain.lvTachesActivesUpdateObjects(const Sender: TObject;
const AItem: TListViewItem);
begin
SetItemColor(AItem);
end;
procedure TfrmMain.SetItemColor(const AItem: TListViewItem; const UpdateColor:
Boolean = False);
var
LObject: TListItemImage;
VC: TColor;
begin
LObject := AItem.Objects.FindObjectT<TListItemImage>('Couleur');
VC:= dtmMain.qrTaches.FieldByName('couleur').AsInteger;
if LObject.Bitmap = nil then
begin
LObject.Bitmap := FMX.Graphics.TBitmap.Create(10,240);
LObject.Bitmap.Clear(VC);
end else if UpdateColor then LObject.Bitmap.Clear(VC);
end;
Is there a better way to proceed? I was also looking to use style but it appears (or I didn't find) that itemlistview can apply styles!
Ps : Firemonkey / Windows / Delphi Berlin XE10.1
I'm using Delphi 7 so take this with a grain of salt.
You may have to write your own CustomDrawItem method on your TreeView to handle this stuff
This is mine (I edited out some code because it has some lengthy logic behind). Also, I don't draw icons so the DrawImage part is commented.
procedure TVentanaVisorComponentes.TreeView1CustomDrawItem(
Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState;
var DefaultDraw: Boolean);
var
NodeRect: TRect;
EsSeleccion, EsDespejado: boolean;
begin
with TreeView1.Canvas do
begin
//If DefaultDraw it is true, any of the node's font properties can be
//changed. Note also that when DefaultDraw = True, Windows draws the
//buttons and ignores our font background colors, using instead the
//TreeView's Color property.
DefaultDraw := False;
//DefaultDraw = False means you have to handle all the item drawing yourself,
//including the buttons, lines, images, and text.
if not DefaultDraw then
begin
Brush.Color := clMenuHighLight;
Font.Color := clWhite;
NodeRect := Node.DisplayRect(True);
FillRect(NodeRect);
// ...
NodeRect := Node.DisplayRect(False);
// ...
FillRect(NodeRect);
NodeRect.Left := NodeRect.Left + (Node.Level * TreeView1.Indent);
//NodeRect.Left now represents the left-most portion of the expand button
DrawButton(NodeRect, Node);
NodeRect.Left := NodeRect.Left + TreeView1.Indent;
//NodeRect.Left is now the leftmost portion of the image.
//DrawImage(NodeRect, Node.ImageIndex);
// NodeRect.Left := NodeRect.Left + ImageList.Width;
//Now we are finally in a position to draw the text.
TextOut(NodeRect.Left, NodeRect.Top, (Node as TNodoArbolComponentes).Texto);
end;
end;
end;
I want to implement a search function in my virtualtreeview. And I want to highlight or underline the searched word in the nodes.
How can I do this?
Thank you
I would write a handler for the OnDrawText event because it's the only event (at this time) where you'll get passed the node text, the rectangle where that text is about to be rendered as well as the canvas prepared for such rendering. There are more proper events for both tasks (like OnBeforeCellPaint, or OnAfterItemErase for text background highlighting, and OnAfterCellPaint or OnAfterItemPaint for text underlining), just none of them provide text rendering specific parameters as the OnDrawText one.
If your nodes won't be multiline and you don't care about text alignment, reading orientation, nor string shortening, then your task might be as easy as one of the following examples.
1. Matching text background color
procedure TForm1.VirtualTreeDrawText(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
Node: PVirtualNode; Column: TColumnIndex; const Text: string; const CellRect: TRect;
var DefaultDraw: Boolean);
var
BackMode: Integer;
begin
// if the just rendered node's Text starts with the text written in a TEdit control
// called Edit, then...
if StartsText(Edit.Text, Text) then
begin
// store the current background mode; we need to use Windows API here because the
// VT internally uses it (so the TCanvas object gets out of sync with the DC)
BackMode := GetBkMode(TargetCanvas.Handle);
// setup the color and draw the rectangle in a width of the matching text
TargetCanvas.Brush.Color := clYellow;
TargetCanvas.FillRect(Rect(
CellRect.Left,
CellRect.Top + 1,
CellRect.Left + TargetCanvas.TextWidth(Copy(Text, 1, Length(Edit.Text))),
CellRect.Bottom - 1)
);
// restore the original background mode (as it likely was modified by setting the
// brush color)
SetBkMode(TargetCanvas.Handle, BackMode);
end;
end;
An example visual output:
2. Matching text underline
procedure TForm1.VirtualTreeDrawText(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
Node: PVirtualNode; Column: TColumnIndex; const Text: string; const CellRect: TRect;
var DefaultDraw: Boolean);
begin
// if the just rendered node's Text starts with the text written in a TEdit control
// called Edit, then...
if StartsText(Edit.Text, Text) then
begin
TargetCanvas.Pen.Color := clRed;
TargetCanvas.MoveTo(CellRect.Left, CellRect.Bottom - 2);
TargetCanvas.LineTo(
CellRect.Left + TargetCanvas.TextWidth(Copy(Text, 1, Length(Edit.Text))),
CellRect.Bottom - 2
);
end;
end;
And an example visual output:
In real code I'd suggest pre-calculating those highlight shapes and in the OnDrawText event only draw, but optimization I would leave on you; the main point is the event itself, I think.
Little modification. Pay attention to if.
var
BackMode: integer;
begin
inherited;
// if the just rendered node's Text starts with the text written in a TEdit control
// called Edit, then...
if StartsText(Sender.SearchBuffer, Text) and (Node = Sender.FocusedNode) then
begin
TargetCanvas.Pen.Color := clRed;
TargetCanvas.MoveTo(CellRect.Left, CellRect.Bottom - 2);
TargetCanvas.LineTo(
CellRect.Left + TargetCanvas.TextWidth(Copy(Text, 1, Length(Sender.SearchBuffer))),
CellRect.Bottom - 2
);
end;
end;