This is my code:
procedure TfrmMain.vstListPaintText(Sender: TBaseVirtualTree;
const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
TextType: TVSTTextType);
begin
if vsSelected in Node.States then
begin
TargetCanvas.brush.color := clBlue;
TargetCanvas.FillRect(targetcanvas.ClipRect);
end;
end;
But this is what happens:
I click the node
Whole VST is painted blue except for the previous selected node
The selected node is blue (and the VST is back to it's default color)
How do I avoid #2?
wrong event if you want to paint the cell - ...PaintText is for setting color and font styles.
Try other events instead (OnBeforeCellPaint) and you will get TRect for the cell automatically.
Simple: you're FillRect-ing the whole canvas. Don't do that. Use OnAfterCellPaint or OnAfterItemPaint. In these events, you get the particular CellRect to do your custom painting.
Related
Previously I used this VirtualStringTree for showing all of the nodes, and I used the Node.Index to check the odd and even rows inside the OnBeforeCellPaint event.
But when I filtered the nodes, I realized that the Node.Index is irrelevant to be used as alternate rows as shown in the screenshot below:
Any idea/solution to solve this?
procedure TMainForm.IpTreeBeforeCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
begin
if Node.Index mod 2 = 0
then TargetCanvas.Brush.Color := $00F7E6D5
else TargetCanvas.Brush.Color := $00FBF2EA;
if Sender = ipTree then
if IpAddresses[ PVirtualNode( Node ).Index ].Highlighted then
TargetCanvas.Brush.Color := clYellow;
TargetCanvas.FillRect( CellRect );
end;
Heres my code, this doesnt take into account child nodes. It alternates color for each row. However, if you did have children, you can always use
iLevel := Sender.GetNodeLevel( Node );
then if its an even number, paint all the child nodes the same as parent.
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;
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.
What is an efficient way to change buttons (bands) in the CoolBar (the red rectangle) while switching among items in the TreeView (the purple rectangle). I want to use one set of buttons for every item in the list view.
Thanks for help and advices!
I'd create the CoolBands I'd need and assign each to the Data pointer of the TTreeNode for which it is to be used. Then in the TreeView's OnChanging handler, I'd "remember" the TreeNode that is currently selected and switch visibility on the CoolBands in the OnChange handler:
procedure TProbeerForm.TreeView1Changing(Sender: TObject; Node: TTreeNode;
var AllowChange: Boolean);
begin
FOldNode := TreeView1.Selected;
end;
procedure TProbeerForm.TreeView1Change(Sender: TObject; Node: TTreeNode);
begin
TCoolBand(FOldNode.Data).Visible := False;
TCoolBand(Node.Data).Visible := True;
end;
Well, I have the following problem:
I've painted the tree cells in different colors depending on some boolean vars.
Example:
isProcessService,
isProcessInDebugger,
isProcessService,
isProcessElevated,
isProcessNet,
isProcessOwner,
isProcessinJob,
isProcessPacked,
isProcessMarkedForDeletion,
isProcessMarkedForCreation : Boolean;
So in BeforeCellPaint I'll paint the cells background color based on those booleans like:
procedure TMainForm.ProcessVstBeforeCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
var
NodeData: PProcessData;
begin
if Node = nil then
Exit;
NodeData := Sender.GetNodeData(Node);
if NodeData = nil then
Exit;
if (NodeData^.isProcessOwner) then
begin
TargetCanvas.Brush.Color := $00AAFFFF;
TargetCanvas.FillRect(TargetCanvas.ClipRect);
end;
if (NodeData^.isProcessInDebugger) then
begin
TargetCanvas.Brush.Color := $00E5A5A5;
TargetCanvas.FillRect(TargetCanvas.ClipRect);
end;
if (NodeData^.pProcessID = 0) or (NodeData^.pProcessID = 4) then
begin
TargetCanvas.Brush.Color := $00FFCCAA;
TargetCanvas.FillRect(TargetCanvas.ClipRect);
end;
if (NodeData^.isProcessElevated) and not(NodeData^.isProcessInDebugger) then
begin
TargetCanvas.Brush.Color := $0000AAFF;
TargetCanvas.FillRect(TargetCanvas.ClipRect);
end;
if (NodeData^isProcessService) and
not (NodeData^.isProcessPacked) and
not(NodeData^.isProcessNet) then
begin
TargetCanvas.Brush.Color := $00FFFFCC;
TargetCanvas.FillRect(TargetCanvas.ClipRect);
end;
if (NodeData^.isProcessMarkedForDeletion) then
begin
TargetCanvas.Brush.Color := $005D5DFF;
TargetCanvas.FillRect(TargetCanvas.ClipRect);
end;
if (NodeData^.isProcessMarkedForCreation) then
begin
TargetCanvas.Brush.Color := $0061E15E;
TargetCanvas.FillRect(TargetCanvas.ClipRect);
end;
if (NodeData^.isProcessNet) then
begin
TargetCanvas.Brush.Color := $005CE0BF;
TargetCanvas.FillRect(TargetCanvas.ClipRect);
end;
end;
The question is:
How could I paint the cell green or red depending on a process is going to be created or deleted (let the color stay for at least one second and then switch back to its original value?)
I other words, a process is created paint the cell green wait a second and then switch back to the original color depending on: isProcessService, is ProcessOwner and so on...
The biggest Problem is I need this in a non blocking mode (I can not use sleep otherwise the tree will freeze too so the color change will not be noticed)
If you still can not follow me, I'm trying to mimic the same behavior Process Explorer or Process Hacker does when a process is created or deleted. Both applications paints the cell background for those processes red or green for a second then switching back to the original color the cell had.
Just for information, I'll get notified of process creation or deletion via wmi.
Whenever a process is created, start a timer associated with that process with a timeout of 1s. The isProcessMarkedForCreation is set to true and so the row is painted green. When the timer fires the handler sets isProcessMarkedForCreation to false and forces a repaint of that row which removes the green highlight. Now that the timer has done its work it should be deleted. The exact same approach can be used for deletion.