ListView and coloring cellls - delphi

I have ListView (vsReport) and StringGrid and what I want is if I click on some element in ListView, particular cells in StringGrid have to change colors. How do I do it?
Path is filled with 1 (move up) and 0(move right), it starts in left bottom and ends in right top corner, and I have to color these cells.
Thanks for the answers, I handled with my problem, but there's another little issue, how can I leave text in cells visible? FillRect fills the entire cell.
procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem; Selected: Boolean);
var aRect: TRect;
a,x,y:integer;
path:string;
begin
path:=ListView1.Items[Item.Index].Caption;
x:=0;
y:=StringGrid1.RowCount;
for a := 0 to length(path) do
begin
if path[a]='1' then y:=y-1 else x:=x+1;
aRect := StringGrid1.CellRect(x-1,y-1);
StringGrid1.Canvas.Brush.Color := clBlue;
StringGrid1.Canvas.FillRect(aRect);
end;
end;

Realize that a cell's color change should be permanent, so that when the StringGrid is painted again, e.g. when the StringGrid was obfuscated by a dialog, also the special colors should be painted again.
Thus you need to store the desired colors somewhere. Say you want to use an array for that, then make a choice between:
Storing the special colors along with the grid coordinates in a one-dimensional array. This is good for memory usage, but you would need to search this entire array for the specific coordinate which the StringGrid's OnDrawCell handler (see step 3) provides,
Storing only the special colors in a two-dimensional array. This is good for speed when drawing, but you need to synchronize the array's column and row bounds to that of the StringGrid,
Or, when you do not need the Objects property of the StringGrid for any purpose, you could employ this property for color storage by typecasting the color to and from a TObject. Shout if you need help with that.
Paint the colored cells in a StringGrid's OnDrawCell event handler (search here on Stack Overflow for [Delphi] StringGrid OnDrawCell when in need of assistance with that).
The ListView's OnSelectItem event exposes the Item which is clicked or otherwise selected.
Retrieve necessery information from that item or its sub-items to determine which cell is to be changed in what color.
Add that information to the chosen storage solution of step 2.
Realize that when all painting now is done "automatically", just a call to StringGrid.Repaint should be enough.

Related

Highlight controls in themed Delphi App with Delphi Tokyo

Using Delphi Tokyo 10.2, with Stylized Themes. I am trying to highlight components on the form, e.g., ComboBoxes, EditTexts, etc. For example, if a user entered invalid data, I would like to highlight the component.
In the past, we just colored components Red, and the color persisted through resizes/movement/repaints in general. Now with theming, we need to do a bit more to get the color to show and persist.
I have tried disabling each component's StyleElements [seFont, seClient, seBorder] properties to force show the color. This works but seems kludgy, particularly when there are many components being validated. Also, simply coloring a component red might not look right with some of the themes.
I have also tried simply drawing a red rectangle around the components using WinAPI SetRop2(..). E.g., here is some clever code, I tweaked to take a TWinControl and Draw a redbox around it; I can also remove the redbox using a similar call. This works:
…but doesn't persist through repaints, obviously. It seems like adding custom paint methods might be an overkill here. Unless, there is some better way?
Other things I have considered:
All of the components sit on panels, and I have considered using a protected hack to draw red rects on the panel's canvas around the components, but again, more custom paint routines…
I am also considering drawing TShapes dynamically as needed, but this strikes me as silly.
There must be others in the same situation, e.g., data entry validation that worked neatly in older versions of Delphi, but doesn't look so good when themed. What is the best approach when using themes? The SetRop2(..) approach seems to be the cleanest, but can someone suggest a simple way to make the color persist? I would welcome other ideas, too. Thank you.
EDIT
So maybe, just dynamically drawing TShapes around the invalid responses isn't so bad. They persist through repaints and don't descend from TWinControl, meaning they automatically show up behind the control they are highlighting.
This works quite well for me and I hope it's helpful to others.
// assuming owning control will be free'd properly and
// will in turn free HI_LITE Box.
//
// tantamount to adding an instance variable, TShape, to existing Control,
// since class helpers don't allow. And I don't want to descend
// new controls just to have a hiLiteBox Instance Variable.
procedure HiLiteMe(aControl : TWinControl; HILITE_FLAG : Boolean = TRUE; aColor : TColor = clRed);
const OFFSET = 4; // specify the offset of the border size of the box.
const BOX_NAME_PREFIX = 'HI_LITE_BOX_';
var
hiLiteBox : TShape; // reference created on stack, but object created on the heap,
uniqueBoxName : String; // so use the persistent aControl's owned component list to maintain the reference.
begin
uniqueBoxName := BOX_NAME_PREFIX + aControl.Name; // uniquename for each associated HiLiteBox.
HiLiteBox := aControl.FindComponent(uniqueBoxName) as TShape; // phishing for the HiLiteBox if it was previously created.
if NOT Assigned(hiLiteBox) then // create HiLiteBox and make persist outside this proc.
begin
if NOT HILITE_FLAG then exit; // don't create a box if we're just going to hide it anyway.
hiLiteBox := TShape.Create(aControl); // Create HiLiteBox, setting aControl as owner, quicker retrieval using aControl.findComponent
hiLiteBox.Parent := aControl.Parent; // Render the box on the control's parent, e.g., panel, form, etc.
hiLiteBox.Name := uniqueBoxName;
hiLiteBox.Pen.Color := aColor; // Color the Pen
hiLiteBox.Pen.Width := offset-1; // Make the Pen just slightly smaller than the offset.
hiLiteBox.Brush.Color := clWindow; // Choose a brush color, to fill the space between the pen and the Control
hiLiteBox.Left := aControl.Left - offset;
hiLiteBox.Width := aControl.Width + offset*2;
hiLiteBox.Top := aControl.Top - offset;
hiLiteBox.Height := aControl.Height + offset*2;
end;
hiLiteBox.Visible := HILITE_FLAG; // Show/Hide HiLite as appropriate.
end;
Called like this to HiLite with a red and blue box...
begin
HiLiteMe(checkListBox1, TRUE, clRed); // Draw a RedBox around the CheckListBox, e.g., Invalid.
HiLiteMe(bitBtn3, TRUE, clBlue); // Draw a Blue Box around the Button, e.g., Required.
end;
Called like this to remove HiLites…
begin
HiLiteMe(checkListBox1, FALSE); // Draw a RedBox around the CheckListBox, e.g., Invalid.
HiLiteMe(bitBtn3, FALSE); // Draw a Blue Box around the Button, e.g., Required.
end;
I suggest having a red TShape on only one side of the control (e.g. just the left or bottom) that you show or hide.

How to add a Timage to a TScrollBox in Firemonkey XE6?

Firstly sorry if this has come up before but I am struggling to find anything on the matter.
I'm trying to add a number of TImage's to a scrollbox which is meant to hold the images and allow the user to scroll across them. This creation is done in run time.
The images are stored in an array of TImage.
Below is the code I have to create the images.
procedure TfrmMain.CreateSolutionImages(ImageCount: Integer);
var
I: Integer;
ImageScale: double;
begin
if sbSolutionImages.ComponentCount > 0 then //destroy the images already in the scrollbox
sbSolutionImages.DestroyComponents;
SetLength(SolutionImages,0); //clear the array of images
SetLength(SolutionImages,ImageCount); //SolutionImages is an array of timage
ImageScale:= ((sbSolutionImages.Width - 20)/Guillotine.StockWidth);
for I := 0 to ImageCount - 1 do
begin
if not Assigned(SolutionImages[I]) then //if not assigned then create and set the parent to the scrollbox
begin
SolutionImages[I]:= TImage.Create(sbSolutionImages);
SolutionImages[I].Parent:= sbSolutionImages;
SolutionImages[I].Width:= trunc(Guillotine.StockWidth * ImageScale); //set image dimentions and positions
SolutionImages[I].Height:= trunc(Guillotine.StockHeight * ImageScale);
SolutionImages[I].Position.X:= 10;
if I = 0 then
begin
SolutionImages[I].Position.Y:= 10;
end
else
begin
SolutionImages[I].Position.Y:= SolutionImages[I-1].Position.Y + SolutionImages[I-1].Height + 20;
end;
end;
//forgot to include these lines
SolutionImages[I].Bitmap.SetSize(Round(SolutionImages[I].Width),Round(SolutionImages[I].Height));
SolutionImages[I].Bitmap.Clear(TAlphaColors.White);
end;
end;
What is happening is that the scrollbox (sbSolutionImages) is reporting that it contains the images, i.e. componentcount increases, however it is not drawing the images and no scrollbars appear, which should logically happen as some of the images won't be in the viewable region.
Any help would be greatly appreciated.
Add a TLayout as a child of the TScrollBox.
Set the Width and Height as appropriate (and set Position=(0,0)).
Add your images as children on the TLayout.
The TScrollBox will then know the bounds of the TLayout and will set it's scroll bars based on this.
Ok sorry. It was a simple stupid issue.
I forgot to set the sizes on the bitmaps of all the images.
Still within the for loop I needed to add.
SolutionImages[I].Bitmap.SetSize(Round(SolutionImages[I].Width),Round(SolutionImages[I].Height));
SolutionImages[I].Clear(TAlphaColors.White);
Ok so it appears that I am still having a problem. The scrollbars are not coming up and trying to the resize the scrollbox (I have a slider between two panels, one is the parent of the scrollbox and the other holds other components) either does nothing (nothing moves) or causes the slider to shoot off the screen to the left, thus hiding everything "off" the application window.
As I am not familiar with firemonkey, this is boggling. I could've done this easily in VCL however we are trying to explore the "acclaimed power" of firemonkey.

Prevent delphi from redrawing columns

I have a string grid where user can change colors of columns. I'm storing a color in a string it looks likethis : columnToColor:= '1;233,233,233' 1 is the column 233;233;233 is a rgb color; I change this string everytime i have to change colors. It never contains more than one column and one color
In my drawcellevent i'm doing this:
color := Explode(';',columnToColor); //this will return an array
if (length(color)-1 >= 0) then
begin
if TryStrToInt(color[0], val) then
begin
if aCol = StrToInt(color[0]) then
begin
cellText := grid.Cells[aCol,aRow];
grid.Canvas.Brush.Color := TColor(RGB(StrToInt(color[1]),StrToInt(color[2]),StrToInt(color[3])));
rec := grid.CellRect(aCol,aRow);
grid.Canvas.FillRect(rec);
grid.Canvas.TextOut(rec.Left,rec.top,cellText);
end;
end;
end;
I'm calling invalidateCol from another procedure using a hacked StringGrid class:
With TCustomStringGrid(grid) do
InvalidateCol(grid.col)
This works when i change only one column color. I can scroll freely trought the grid and it will still be there with the good column color. But when i change the color of another column the colors are shown when their are still visibile. Once i scroll horizontally and get back to the columns only the last colored column is colored and other are set to default color. The color only stays on the last colored column. So if i color 2 columns and i click on the first one, the cell's color is set to default. And i scroll horizontally the whole column 1 is set to default color. Only the second column keep its color what ever i do.
How can i fix this pls?
This is perhaps becoming a little more clear to me. The problem is that you want the grid control to paint each column in separate colors. Although your columnToColor string specifies the color for only a single column, you want each column to have, potentially, a different color. When you scroll the grid, and columns are re-painted, only the column specified in columnToColor has the desired color.
All this is happening because Windows controls need to be able to re-paint themselves completely at any time. Once you have painted a control, the control does not remember its state. If it becomes invalid (control dragged over, scrolled, etc.) then the control must be able to re-paint itself in its entirety.
Your code fails to do that. Since it only remembers the color for the most recently modified column, when it needs to paint other columns, they get the default color. Your approach cannot succeed since the control only keeps track of one single column color.
The solution is simple enough. You need to remember the color for each column. An obvious way to do so would be to hold the colors in an array:
FColumnColor: array of TColor;
or
FColumnColor: TArray<TColor>;
in a more modern Delphi. Or perhaps even TList<TColor>.
When you need to change a column's color do so by modifying FColumnColor[ColIndex]. Likewise, whenever you need to paint, read the color out of FColumnColor[ColIndex].

Delphi TStringGrid Background color missing on left side [duplicate]

FillRect doesn't paint the complete TStringGrid cell in Delphi XE2. There is a gap of 3 pixels on the left side in the default color (with BiDiMode set to bdLeftToRight). This problem doesn't exist in Delphi 6 which I used before.
procedure TShapeline.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
begin
Stringgrid1.Canvas.Brush.Color:=$00FF80FF;
StringGrid1.Canvas.FillRect(Rect);
end;
I tried to change all properties (including the DrawingStyle) and different brush styles, the painted rectangle doesn't fill the complete cell.
This is expected behaviour in XE2 when DefaultDrawing = true and themes are enabled (I'm not going to argue about good or bad here - as you might have noticed, the behaviour is different for RigthToLeft mode...).
A workaround is to check for this condition and decrement Rect.Left by 4 pixel before calling FillRect.
You can use the StringGrid1.CellRect(ACol, ARow) that returns the actual TRect of the cell instead of using the parameter Rect.
Turn off the first 4 options in TStringGrid:
goFixedVertLine
goFixedHorizLine
goVertLine
goHorizLine
Then it won't paint the grid lines, and your grid cells will paint right to the edges. Just tried it with XE.
Since you're drawing the grid cell yourself then just turn off the grid property DefaultDrawing, set it to false.

How do I make a memo column on a DX grid show partial words?

I've got a TdxDBGrid that's displaying some information retrieved from a database query. One of the columns is a Memo column, (TdxDbGridMemoColumn,) which is necessary because the data in the field it's bound to comes out of the database as type TEXT, not CHAR or VARCHAR.
Problem is, the memo column likes to display whole words, and if it can't display a whole word, it doesn't display any part of it. The normal grid columns show everything they can up to the right border and cut off the display there, but the memo column doesn't, and that's bound to confuse end-users. Is there any way I can get the memo column to display partial words?
You could owner-draw the column. Then you can make the text look however you want. Call DrawText and use the dt_End_Ellipsis flag to draw an ellipsis on the end of long text, or else just let the long text be clipped to the drawing area.
in the onGetText event of the column, you can modify the displayed text to accommodate the available size:
// the TTextFormats flags are defined in Graphics, add it to your uses clause
procedure TMyForm.gridMyColGetText(Sender: TObject; ANode: TdxTreeListNode;
var AText: string);
var
R: TRect;
begin
// Calculate actual displayable text (with ellipsis) depending on cell size
R := (Sender as TdxDBGridColumn).TreeList.CellRect(ANode, (Sender as TdxDBGridColumn).ColIndex); // get the cell rectangle
Windows.InflateRect(R, -2, 0); // shrink a bit for grid lines
grid.Canvas.TextRect(R, AText, [tfModifyString, tfEndEllipsis]); // shorten the text ...
end;

Resources