How Can I Paint All The Column Title Cells Of A TStringGrid Different Colours? - background-color

I am running Lazarus 0.9.30.2.
I have a TForm on which there is a TStringGrid. Each column title is a TGridColumns object that I added dynamically to the grid at run time. Each column title has an object associated with it (that I created and have stored in a TList). I want to paint the background of the column title cells of the string grid, but I don't want all of the cells to be the same colour. Depending on the value of one of the properties in the object associated with the column title, the colour will vary.
I know that there are answers regarding how to paint TStringGrid cells in Stackoverflow (example), that talk about using the string grids DrawCell event to paint the cells, but I'm not sure how to invoke this procedure.
Is the correct approach to have another procedure that identifies the cell of interest (ie identifies the cells 'Rect' property), sets the colour that I want, that then invokes a common DrawCell procedure of the grid to do the actual colouring?

There's a better event for this purpose, the OnPrepareCanvas. This event is being fired whenever the cell is preparing to draw itself and in that stage you can modify some of the canvas attributes, like brush color for painting the background. So what you need is to store the color somewhere:
type
TTmColumnTitle = class(TTmObject)
private
FCellColor: TColor;
public
property CellColor: TColor read FCellColor write FCellColor;
end;
And write the handler for the OnPrepareCanvas event:
procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;
aState: TGridDrawState);
var
ColumnTitle: TTmColumnTitle;
begin
if ARow = 0 then
begin
ColumnTitle := TTmColumnTitle(StringGrid1.Objects[ACol, ARow]);
if Assigned(ColumnTitle) then
StringGrid1.Canvas.Brush.Color := ColumnTitle.CellColor;
end;
end;
Object Inspector with OnPrepareCanvas event shown:

Related

How to increase the size of a checkbox?

I'm trying to increase the size of a checkbox in my Userform.
In the properties tab, I can change the height and the width of the object but it doesn't change the size of the square. I add a picture to explain my issue.
Thank you.
#Portland Runner's comment is a good suggestion. For example, in the click event of the label (using WingDings 2) ...
Option Explicit
Private Sub Label1_Click()
If Label1.Caption = "Q" Then
Label1.Caption = "R"
Else
Label1.Caption = "Q"
End If
End Sub
There are 2 problems with the VBA checkbox:
The size of the square
The size of the caption text
My solutions:
Create a frame in which you put the checkbox object. A frame has the property Zoom. Set that property at whatever value you want. Then change the font size of the button to match the rest of the fonts in the form. The frame doesn't have to have a title, and you can select an invisible border for it. In that way, the user doesn't see it.
For whatever reason, the checkbox's text looks smaller than the rest of the objects, even though it is the same font size. My solution for this was to remove the checkbox's text and add a standard label object to the right.

On Delphi how to I reference objects that have been created using just one variable?

I was creating a Form with some buttons in runtime in my application and I realized something that's bothering me a lot and I couldn't figure for myself or anywhere in the internet.
Look at the following code.
procedure TfrmTest.CreateFourButtons(Sender: TObject);
var
i: Integer; B: TButton;
begin
for i := 1 to 4 do
begin
B := TButton.Create(frmTest);
B.Parent := frmTest;
B.SetBounds(250,(70+(30*i)),75,25);
B.Caption := 'Button' + IntToStr(i);
B.Visible := True;
end;
end;
So I just created four buttons at runtime on a form in specific locations. So far so good right?
But now let's imagine that I want to change the Caption property of the first button to "HotPotato"? How do I reference the first button now since I used just one variable to create those buttons? I've been told to store those objects in an array of TButtons or better yet in an TObjectList and these are all fine solutions to this problem. But then one question came to my mind!
Where are those buttons located in the memory? Are the any ways for me to reference them without using arrays or object lists?
The normal way to keep track of multiple similar object is to use an array.
procedure TfrmTest.CreateFourButtons(Sender: TObject);
var
i: Integer;
B: TArray<TButton>; //or array of TButton for older versions
begin
SetLength(B, 4);
for i := 0 to 3 do begin
B[i] := TButton.Create(frmTest);
B[i].Parent := frmTest;
B[i].SetBounds(250,(70+(30*i)),75,25);
B[i].Caption := 'Button' + IntToStr(i);
B[i].Visible := True;
end;
B[0].Caption:= 'HotPotato';
end;
When placing buttons on a form this is not strictly needed, the form already uses a list to keep track of child controls placed on it, but you'll need some way to tell the different buttons apart.
You can use the tag property for this:
for i := 1 to 4 do begin
B := TButton.Create(frmTest);
B.Parent := frmTest;
B.Tag:= i;
...
end;
//This will get inefficient if there are many controls on a form.
for var C in frmTest.Controls do begin //10.3 syntax.
if (C is TButton) and (C.tag = 1) then C.Caption:= 'HotPotato'
end;
The button is an object and it is thus located on the heap. If you lose its reference you will never find it again. However if it is placed on a parent control, then that parent will keep track of it and you can always get it using FindChildControl or the Controls list of the form.
FindChildControl does a search by name. This requires you to set the name of the control, or it will not work.
var B:= frmTest.FindChildControl('Button1');
Note: FindChildControl only locates immediate children of the control. It can't find a control that is a child of one of the control's children.
This locating children can get complicated if the button is located in a subpanel. Better to use the array or a list to keep track of a range of buttons.

Drawing and image on a button in a cell in a TStringGrid

I have a TStringGrid in Lazarus, running on Linux. I have a column which has editor type cbsButton. I want the button to display a certain image, instead of an ellipsis. I have the following code, which causes an error:
procedure TForm1.streams_gridDrawCell(Sender: TObject; aCol, aRow: Integer; aRect: TRect; aState: TGridDrawState);
var
aCanvas: TCanvas;
aGrid: TStringGrid;
Editor: TWinControl;
image: TImage;
begin
if (aCol <> 1) or (aRow = 0) then begin
Exit;
end;
aGrid := (Sender as TStringGrid);
aCanvas := image.Canvas;
aCanvas.FillRect(aRect);
imagelist1.Draw(aCanvas, aRect.Left+2, aRect.Top+2, 8);
Editor := (aGrid.EditorByStyle(cbsButton) as TButtonCellEditor);
Editor.Brush.Style := TBrushStyle.bsImage;
(Editor.Brush.Image as TImage) := image; // causes the error below
end;
The error is:
mainform.pas(156,23) Error: Class or Object types "TFPCustomImage" and
"TImage" are not related
At this point, I'm sure I'm going about this in entirely the wrong way. Could someone please put me back on the right path?
I doubt that the OnDrawCell event is the correct place to modify a cell editor because probably the correct cell editor does not exist at this moment when the cell is painted.
The correct event to define the cell editor is the OnSelectEditor event of the grid. Please read the wiki (http://wiki.lazarus.freepascal.org/Grids_Reference_Page).
The cbsButton editor which you use inherits from TButton. A TButton does not have a Glyph property - you cannot assign a bitmap to the button. But can you write your own cell editor easily, just follow the standard example in examples/gridexamples/gridcelleditor:
Add a TBitBtn to the form. Delete its Caption, add the requested image to the Glyph property. Set the Visible property to false.
In the OnClick event of this button write how you want to edit the cell. Access the cell specified by the properties Col and Row of the grid. As an example, I assume here that you just want to open an InputBox:
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
StringGrid1.Cells[StringGrid1.Col, StringGrid1.Row] :=
InputBox('Input some text', 'Text:', '');
end;
Now write an event handler for the OnSelectEditor event of the grid. It must assign the BitBtn to the Editor parameter of the event and make sure that the button is at the correct position within the selected cell - that's all!
procedure TForm1.StringGrid1SelectEditor(Sender: TObject; aCol, aRow: Integer;
var Editor: TWinControl);
var
R: TRect;
begin
if (aCol=2) and (aRow > 0) then begin
R := StringGrid1.CellRect(aCol, ARow);
R.Left := R.Right - (R.Bottom - R.Top);
BitBtn1.BoundsRect := R;
Editor := BitBtn1;
end;
end;
Editor.Brush.Image is a property of type TFPCustomImage. This is a TPersistent descendant. And TImage is a descendant of TCustomImage and thus TGraphicControl and TControl. So these are entirely different classes that are not compatible.
So you are not expected to cast (Editor.Brush.Image as TImage) and assign any TImage instance to it.

InnoSetup, How to load a custom text into RTFText

I'm trying to store the text of InfoBefore textfile into a variable and then load it into the RTFEditor with a custom font color and backcolor.
When I try to load the text from the variable it says "Write-only property"
I need a explicit example of how to do this two things together (Store the text in the var, load the text in the RTF with a custom color and backcolor) without complicating the things too much because I don't know Pascal.
This is the code:
const
FontColor: AnsiString = 'cf0';
BackColor: AnsiString = 'cf1'
var
OldText: AnsiString;
procedure InitializeWizard();
begin
// I try to store the text in a variable
Oldtext := WizardForm.InfoBeforeMemo.RTFText;
// I try to load from the variable, with a new font color for ALL the text, and a new BackColor.
WizardForm.InfoBeforeMemo.RTFText := Oldtext + FontColor
end;
Instead of trying to modify it after the fact, simply load your .txt file in WordPad, make the formatting changes you require, then save it in .rtf format. You can then use this file as your InfoBeforeFile directly, without using any code.
Edit: if you want to change the background colour of the entire memo then you'll still need one line of code. For example:
[Code]
procedure InitializeWizard();
begin
WizardForm.InfoBeforeMemo.Color := clBlack;
end;

Delphi 6 - Create Excel chart from delphi application - data and chart on the same page

I need to create an excel chart in an excel page from a delphi program.
I am getting 'Member not found' error on reaching the line.
ch1.Chart.SeriesCollection.Item[0].Values := Sheets.Item['Delphi Data'].Range['E5:E15'];
Can you please help me to solve it
Below given is the code used.
procedure TForm1.ChartData;
var
ARange,Sheets,ch1 : Variant;
SSeries : Series;
num : integer;
ChartAxis : Axis;
lcid : Cardinal;
begin
ch1 := XLApp.ActiveWorkBook.Sheets[1].ChartObjects.Add ( 500,100,400,200 ); // creates a new chart in the specified
Sheets := XLApp.Sheets;
ch1.Chart.ChartWizard (
Sheets.Item['Delphi Data'].Range['D5:D15'], // 1 Source
xlBarStacked, // 2 The chart type.
8, // 3 Format
2, // 4 PlotBy
8, // 5 CategoryLabels
3, // 6 SeriesLabels
True, // 7 HasLegend - 'true' to include a legend.
'Sijos Report', // 8 Title - The Chart control title text.
'Y Legend', // 9 CategoryTitle - The category axis title text.
'X Legend', // 10 ValueTitle - The value axis title text
2 // 11 ExtraTitle - The series axis title for 3-D charts or the second value axis title for 2-D charts.
);
ch1.Chart.SetSourceData(Sheets.Item['Delphi Data'].Range['D5:D15'],xlColumns);
ch1.Chart.SeriesCollection.Item[0].Values := Sheets.Item['Delphi Data'].Range['E5:E15'];
ch1.Chart.SeriesCollection.Item[0].XValues := Sheets.Item['Delphi Data'].Range['F5:F15'];
End;
I think you need ch1.Chart.SeriesCollection(1) rather than ch1.Chart.SeriesCollection.Item[0] since Excel uses 1-based indexing.
I also could not get your code, using late bound COM to work at all when accessing the series object. But if you switch to using early bound COM then it is fine. You will need to add Excel2000, for example, to your uses clause.
var
S: Series;
....
S := IUnknown(ch1.Chart.SeriesCollection(1)) as Series;
S.Values := Sheets.Item['Delphi Data'].Range['E5:E15'];
S.XValues := Sheets.Item['Delphi Data'].Range['F5:F15'];
If I were you I would switch the whole code to early bound.
I think your code is based on this Delphi 3 example from Charlie Calvert. I could not get that to work on my Delphi 6. Perhaps Delphi changed. Perhaps Excel changed. No matter, the thing that made it work for me was to switch to early bound COM.

Resources