I have a problem and need your help
I am going to work on sudoku game. In my Stringgrid I've filled some cells with digits before [grid1.cells[8,8]:=inttostr(2); grid1.cells[2,5]:=inttostr(9); etc] and digits' text font color are black. Now I want player cannot change(edit) previous values and only able to add to empty cells(can change only its own values).
And values inserted into cells have to be diffent text font color(exp: clRed)
I need help in this two cases.
Thanks in advance .
There is no public way to interrupt process of cell editing, but you can make a TStringGrid subclass and override its CanEditShow protected method. In this control subclass, you can e.g. make an event to control whether the inplace editor will be created or not.
The following interposer class introduces the OnCanEdit event which will fire before the inplace editor is created and allows to you decide whether you want to create it or not by its CanEdit parameter:
type
TCanEditEvent = procedure(Sender: TObject; Col, Row: Longint;
var CanEdit: Boolean) of object;
TStringGrid = class(Grids.TStringGrid)
private
FOnCanEdit: TCanEditEvent;
protected
function CanEditShow: Boolean; override;
public
property OnCanEdit: TCanEditEvent read FOnCanEdit write FOnCanEdit;
end;
implementation
{ TStringGrid }
function TStringGrid.CanEditShow: Boolean;
begin
Result := inherited CanEditShow;
if Result and Assigned(FOnCanEdit) then
FOnCanEdit(Self, Col, Row, Result);
end;
This example shows how to allow editing only for cells with row and column index greater than 2, which is not your case, but I'm sure you understand what to do:
type
TForm1 = class(TForm)
StringGrid1: TStringGrid;
procedure FormCreate(Sender: TObject);
private
procedure StringGridCanEdit(Sender: TObject; Col, Row: Longint;
var CanEdit: Boolean);
end;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
StringGrid1.OnCanEdit := StringGridCanEdit;
end;
procedure TForm1.StringGridCanEdit(Sender: TObject; Col, Row: Integer;
var CanEdit: Boolean);
begin
// to the CanEdit parameter assign True if you want to allow the cell
// to be edited, False if you don't
CanEdit := (Col > 2) and (Row > 2);
end;
Although the question is over 4 years old I am answering because the initial Answer is not absolutely correct. In fact there is a way to prevent editing specific cells:
You can set the CanSelect parameter of the TStringGrid OnSelectCell:
procedure TForm1.FormCreate(Sender: TObject);
begin
StringGrid1.Options := StringGrid1.Options+[goEditing];
StringGrid1.Cells[2,3] := '3';
StringGrid1.Objects[2,3] := Pointer(1);
end;
procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
var CanSelect: Boolean);
begin
if StringGrid1.Objects[ACol,ARow]<>nil then
CanSelect := false;
end;
The decision to block a cell can be done by setting a blocking value to the corresponding Objects
Related
Good evening, I would like to know how to change the color of a cell when writing data in it
I have this...
procedure TFrmReportes.SGDrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
begin
if (gdSelected in State) then
begin
SG.Canvas.Brush.Color := rgb(255,119,119);
SG.Canvas.FillRect(SG.CellRect(ACol, ARow));
SG.Canvas.TextOut(Rect.Left+2,Rect.Top+2, SG.Cells[ACol, ARow]);
end;
end;
but when entering data in the cell, it turns white
Thanks Again!!!
TStringGrid displays a TInplaceEdit on top of the cell currently being edited. That TInplaceEdit covers the entire cell. That is why you don't see your custom drawing. You would need to change the TInplaceEdit's Color property instead. You can access the TInplaceEdit via the TStringGrid.InplaceEditor property.
I would suggest deriving a new component from TStringGrid and override its virtual CreateEditor() method. If there is only 1 grid in your Form, a simple interposer would suffice, eg:
type
TStringGrid = class(Vcl.Grids.TStringGrid)
protected
function CreateEditor: TInplaceEdit; override;
end;
TFrmReportes = class(TForm)
SG: TStringGrid;
...
end;
...
type
TInplaceEditAccess = class(TInplaceEdit)
end;
function TStringGrid.CreateEditor: TInplaceEdit;
begin
Result := inherited CreateEditor;
TInplaceEditAccess(Result).Color := RGB(255, 119, 119);
end;
I found the following code that served me thanks to your suggestion in the previous example
with the InplaceEditor property
type
THackGrid = class(TCustomGrid)
public
property InPlaceEditor;
property EditorMode;
end;
TFrmReportes = class(TForm)
SG: TStringGrid;
...
end;
...
procedure TFrmReportes.Button1Click(Sender: TObject);
begin
THackGrid(SG).InPlaceEditor.Brush.Color := RGB(255, 119, 119);
end;
thank you very much
Short Version: Is there any way to control or modify LisBox items individually? for example set their Visible property to False separately.
I found a TListBoxItem class in Fire Monkey when I was searching, but I don't want to use Fire Monkey and want it in VCL.
Detailed Version:
I tried to filter my ListBox using two TStringList and an Edit, one StringList is global to keep the original list (list_files_global) and another StringList to help filtering procedure (list_files_filter) and my primary list of files is my ListBox (list_files).
I created my global StringList on onCreate event while program is starting to store my original list:
procedure Tfrm_main.FormCreate(Sender: TObject);
Begin
list_files_global := TStringList.Create;
list_files_global.Assign(list_files.Items);
End;
and used Edit's onChange event for filtering:
procedure Tfrm_main.edit_files_filterChange(Sender: TObject);
Var
list_files_filter: TStringList;
i: Integer;
Begin
list_files_filter := TStringList.Create;
list_files_filter.Assign(list_files.Items);
list_files.Clear;
for i := 0 to list_files_filter.Count - 1 do
if pos(edit_files_filter.text, list_files_filter[i]) > 0 then
list_files.Items.Add(list_files_filter[i]);
End;
and for switching off the filter, just recover the list from my global list that I created at first:
list_files.Items := list_files_global;
here so far, everything works just fine, but problem is when I'm trying to edit/rename/delete items from filtered list, for example I change an item:
list_files.Items[i] := '-- Changed Item --';
list will be edited, but when I switch off the filter, the original list will be back and all changes are lost.
so I want to know is there any proper way to solve this problem? Something like hiding items individually or change items visibility, etc... so I can change the filtering algorithm and get rid of all this making extra lists.
I searched the internet and looked into Delphi's help file for a whole day and nothing useful came up.
The items of a VCL listbox, List Box in the API, does not have any visibility property. The only option for not showing an item is to delete it.
You can use the control in virtual mode however, where there are no items at all. You decide what data to keep, what to display. That's LBS_NODATA window style in the API. In VCL, set the style property to lbVirtual.
Extremely simplified example follows.
Let's keep an array of records, one record per virtual item.
type
TListItem = record
FileName: string;
Visible: Boolean;
end;
TListItems = array of TListItem;
You can extend the fields as per your requirements. Visibility is one of the main concerns in the question, I added that. You'd probably add something that represents the original name so that you know what name have been changed, etc..
Have one array per listbox. This example contains one listbox.
var
ListItems: TListItems;
Better make it a field though, this is for demonstration only.
Required units.
uses
ioutils, types;
Some initialization at form creation. Empty the filter edit. Set listbox style accordingly. Fill up some file names. All items will be visible at startup.
procedure TForm1.FormCreate(Sender: TObject);
var
ListFiles: TStringDynArray;
i: Integer;
begin
ListFiles := ioutils.TDirectory.GetFiles(TDirectory.GetCurrentDirectory);
SetLength(ListItems, Length(ListFiles));
for i := 0 to High(ListItems) do begin
ListItems[i].FileName := ListFiles[i];
ListItems[i].Visible := True;
end;
ListBox1.Style := lbVirtual;
ListBox1.Count := Length(ListFiles);
Edit1.Text := '';
end;
In virtual mode the listbox is only interested in the Count property. That will arrange how many items will show, accordingly the scrollable area.
Here's the filter part, this is case sensitive.
procedure TForm1.Edit1Change(Sender: TObject);
var
Text: string;
Cnt: Integer;
i: Integer;
begin
Text := Edit1.Text;
if Text = '' then begin
for i := 0 to High(ListItems) do
ListItems[i].Visible := True;
Cnt := Length(ListItems);
end else begin
Cnt := 0;
for i := 0 to High(ListItems) do begin
ListItems[i].Visible := Pos(Text, ListItems[i].FileName) > 0;
if ListItems[i].Visible then
Inc(Cnt);
end;
end;
ListBox1.Count := Cnt;
end;
The special case in the edit's OnChange is that when the text is empty. Then all items will show. Otherwise code is from the question. Here we also keep the total number of visible items, so that we can update the listbox accordingly.
Now the only interesting part, listbox demands data.
procedure TForm1.ListBox1Data(Control: TWinControl; Index: Integer;
var Data: string);
var
VisibleIndex: Integer;
i: Integer;
begin
VisibleIndex := -1;
for i := 0 to High(ListItems) do begin
if ListItems[i].Visible then
Inc(VisibleIndex);
if VisibleIndex = Index then begin
Data := ListItems[i].FileName;
Break;
end;
end;
end;
What happens here is that the listbox requires an item to show providing its index. We loop through the master list counting visible items to find out which one matches that index, and supply its text.
This is something I often do, but with list views instead of list boxes. The basic principles are the same, though.
I tend to store the individual items as objects, which are reference types in Delphi. And I keep them all in one main unfiltered list, which owns the objects, while I maintain a filtered list (which does not own the objects) for display purposes. Like #Sertac, I combine this with a virtual list view.
To see how this works in practice, create a new VCL application and drop a list view (lvDisplay) and an edit control (eFilter) on the main form:
Notice I have added three columns to the list view control: "Name", "Age", and "Colour". I also make it virtual (OwnerData = True).
Now define the class for the individual data items:
type
TDogInfo = class
Name: string;
Age: Integer;
Color: string;
constructor Create(const AName: string; AAge: Integer; const AColor: string);
function Matches(const AText: string): Boolean;
end;
where
{ TDogInfo }
constructor TDogInfo.Create(const AName: string; AAge: Integer;
const AColor: string);
begin
Name := AName;
Age := AAge;
Color := AColor;
end;
function TDogInfo.Matches(const AText: string): Boolean;
begin
Result := ContainsText(Name, AText) or ContainsText(Age.ToString, AText) or
ContainsText(Color, AText);
end;
And let us create the unfiltered list of dogs:
TForm1 = class(TForm)
eFilter: TEdit;
lvDisplay: TListView;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FList, FFilteredList: TObjectList<TDogInfo>;
public
end;
where
function GetRandomDogName: string;
const
DogNames: array[0..5] of string = ('Buster', 'Fido', 'Pluto', 'Spot', 'Bill', 'Rover');
begin
Result := DogNames[Random(Length(DogNames))];
end;
function GetRandomDogColor: string;
const
DogColors: array[0..2] of string = ('Brown', 'Grey', 'Black');
begin
Result := DogColors[Random(Length(DogColors))];
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
FList := TObjectList<TDogInfo>.Create(True); // Owns the objects
// Populate with sample data
for i := 1 to 1000 do
FList.Add(
TDogInfo.Create(GetRandomDogName, Random(15), GetRandomDogColor)
);
FFilteredList := FList;
lvDisplay.Items.Count := FFilteredList.Count;
lvDisplay.Invalidate;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if FFilteredList <> FList then
FreeAndNil(FFilteredList);
FreeAndNil(FList);
end;
The idea is that the list view control always displays the FFilteredList, which either points to the same object instance as FList, or points to a filtered (or sorted) version of it:
// The list view's OnData event handler
procedure TForm1.lvDisplayData(Sender: TObject; Item: TListItem);
begin
if FFilteredList = nil then
Exit;
if not InRange(Item.Index, 0, FFilteredList.Count - 1) then
Exit;
Item.Caption := FFilteredList[Item.Index].Name;
Item.SubItems.Add(FFilteredList[Item.Index].Age.ToString);
Item.SubItems.Add(FFilteredList[Item.Index].Color);
end;
// The edit control's OnChange handler
procedure TForm1.eFilterChange(Sender: TObject);
var
i: Integer;
begin
if string(eFilter.Text).IsEmpty then // no filter, display all items
begin
if FFilteredList <> FList then
begin
FreeAndNil(FFilteredList);
FFilteredList := FList;
end;
end
else
begin
if (FFilteredList = nil) or (FFilteredList = FList) then
FFilteredList := TObjectList<TDogInfo>.Create(False); // doesn't own the objects
FFilteredList.Clear;
for i := 0 to FList.Count - 1 do
if FList[i].Matches(eFilter.Text) then
FFilteredList.Add(FList[i]);
end;
lvDisplay.Items.Count := FFilteredList.Count;
lvDisplay.Invalidate;
end;
The result:
Notice that there always is only one in-memory object for each dog, so if you rename a dog, the changes will reflect in the list view, filtered or not. (But don't forget to invalidate it!)
I know that using a column's readonly property, i can avoid editing its field value. But this doesn't stop the inplace editor to show itself.
I need a way to make the column not only protected but "untouchable".
Is there a way, please ?
If I understand what you want correctly, you can do this quite simply, by creating a custom TDBGrid descendant and overriding
its CanEditShow method, as this determines whether the grid's InplaceEditor can be created:
type
TMyDBGrid = class(TDBGrid)
private
FROColumn: Integer;
protected
function CanEditShow : Boolean; override;
public
property ROColumn : Integer read FROColumn write FROColumn;
end;
function TMyDBGrid.CanEditShow: Boolean;
begin
Result := Inherited CanEditShow;
Result := Result and (Col <> ROColumn);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyDBGrid := TMyDBGrid.Create(Self);
MyDBGrid.ROColumn := 1;
MyDBGrid.DataSource := DataSource1;
MyDBGrid.Parent := Self;
[...]
This minimalist example just defines one grid column by number as being one
where the InplaceEditor is not permitted; obviously you could use any mechanism
you like to identify the column(s) for which CanEditShow returns False.
Note that the code above doesn't account for the fact that the column numbering of the grid changes if you turn off the Indicator column (i.e. set Options.dgIndicator to False);
Obviously, you get more flexibility for customizing which columns are permitted an InplaceEditor by using an assignable event as in
type
TAllowGridEditEvent = procedure(Sender : TObject; var AllowEdit : Boolean) of object;
TMyDBGrid = class(TDBGrid)
private
FOnAllowEdit: TAllowGridEditEvent;
protected
function CanEditShow : Boolean; override;
procedure DoAllowEdit(var AllowEdit : Boolean);
public
property OnAllowEdit : TAllowGridEditEvent read FOnAllowEdit write FOnAllowEdit;
end;
function TMyDBGrid.CanEditShow: Boolean;
begin
Result := Inherited CanEditShow;
if Result then
DoAllowEdit(Result);
end;
procedure TMyDBGrid.DoAllowEdit(var AllowEdit: Boolean);
begin
if Assigned(FOnAllowEdit) then
FOnAllowEdit(Self, AllowEdit);
end;
procedure TForm1.AllowEdit(Sender: TObject; var AllowEdit: Boolean);
var
Grid : TMyDBGrid;
begin
Grid := Sender as TMyDBGrid;
AllowEdit := Grid.Col <> 1;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyDBGrid := TMyDBGrid.Create(Self);
MyDBGrid.ROColumn := 1;
MyDBGrid.DataSource := DataSource1;
MyDBGrid.Parent := Self;
MyDBGrid.OnAllowEdit := AllowEdit;
[...]
If you don't like creating the grid in code, you could put it in a custom package and install it
in the IDE or, if your Delphi version is recent enough, implement the
CanEditShow in a class helper.
How can I make all my grids look the same way all over my forms?
I want to implement an alternate row color that must be applied on all grids of my project. Is it possible without adding the same DrawColumnCell event code for every grid?
I want to avoid adding the same code for each of my grids. I have like 30 grids in my project and multiplied by 13 rows of code it just adds a lot of code lines to my project making it "unfriendly".
I am looking for a solution that will only add 13 lines of code to the project, not 390 lines.
My formatting code looks like this (for example):
procedure TDBGrid.DBGrid1DrawColumnCell(Sender: TObject;const Rect: TRect;DataCol: Integer;Column: TColumn;State: TGridDrawState) ;
var
grid : TDBGrid;
row : integer;
begin
grid := sender as TDBGrid;
row := grid.DataSource.DataSet.RecNo;
if Odd(row) then
grid.Canvas.Brush.Color := clSilver
else
grid.Canvas.Brush.Color := clDkGray;
grid.DefaultDrawColumnCell(Rect, DataCol, Column, State) ;
end;
Probably I would need to extend the DBGrid somehow, but I do not know exactly how nor how to look for a solution for this on google
I tried to hack the DBGRid inside each form like this:
type
TDBGrid = class(DBGrids.TDBGrid)
protected
procedure DrawColumnCell(const Rect: TRect; DataCol: Integer;Column: TColumn; State: TGridDrawState); override;
end;
...
procedure TDBGrid.DrawColumnCell(const Rect: TRect; DataCol: Integer;Column: TColumn; State: TGridDrawState) ;
var
grid : TDBGrid;
row : integer;
begin
row := 2;//grid.DataSource.DataSet.RecNo;
if Odd(row) then
Canvas.Brush.Color := clSilver
else
Canvas.Brush.Color := clDkGray;
DefaultDrawColumnCell(Rect, DataCol, Column, State) ;
end;
I can do this but I cannot access the sender, so I can access the dataset and know which record to color and which not (odd and even).
And this is a poor approach anyways since I will have to do it on every form, so it's not really a solution
Any ideas?
Thank you
If you put something like this in your datamodule, and assign it to the OnDrawColumnCell of every DBGrid, it seems to work (see notes that follow):
procedure TDataModule1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
const
RowColors: array[Boolean] of TColor = (clSilver, clDkGray);
var
OddRow: Boolean;
begin
// Safety check, although it really isn't needed; no other control accepts
// this event handler definition, AFAIK, so the only way to call it with the
// wrong Sender type would be to do so in your own code manually. In my own
// code, I'd simply leave out the check and let the exception happen; if I
// was stupid enough to do so, I'd want my hand slapped rudely.
if (Sender is TDBGrid) then
begin
OddRow := Odd(TDBGrid(Sender).DataSource.DataSet.RecNo);
TDBGrid(Sender).Canvas.Brush.Color := RowColors[OddRow];
TDBGrid(Sender).DefaultDrawColumnCell(Rect, DataCol, Column, State);
end;
end;
A couple of notes:
First, you should avoid using TDataSet.RecNo in the first place, because post-BDE datasets don't typically have this value available. Accessing it (particularly on large or query-based datasets) causes a major performance hit to your application. Of course, not using it means that you can't use this solution. A better solution would be to use a handler for the dataset's BeforeScroll or AfterScroll event that toggled a boolean available to this code instead, and use that instead of the test for Odd(RecNo), or if the dataset is only used for displaying in the DBGrid, use the TDataSet.Tag in the AfterScroll event to track the row's odd/even state using
OddRow := Boolean(DataSet.Tag);
DataSet.Tag := Ord(not OddRow);
Add DBGrids to the uses clause of your datamodule, and manually declare the above event in the published section so that it's available to all units that use the datamodule. You can then assign it in the Object Inspector Events tab as usual from those units.
This does not properly handle the TGridDrawState (nor does your initial code). You'll need to add handling for that yourself, as that wasn't what you asked here.
Depending on which color you want for odd and even rows, you may want to reverse the order of the colors in RowColors.
I prefer the repeated typecasts so that it's clear what the code is doing. If it bothers you, you can simply declare a local variable instead:
var
OddRow: Boolean;
Grid: TDBGrid;
begin
if (Sender is TDBGrid) then
begin
Grid := TDBGrid(Sender);
OddRow := Odd(Grid.DataSource.DataSet.RecNo);
...
end;
end;
This works for Delphi XE7
type
TDBGrid=Class(Vcl.DBGrids.TDBGrid)
procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
end;
procedure TDBGrid.WMVScroll(var Message: TWMVScroll);
begin
Self.Invalidate;
inherited;
end;
procedure TForm1. DBGrid1MouseWheel(Sender: TObject; Shift: TShiftState;
WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
begin
if Sender is TDBGrid then
(Sender as TDBGrid).Invalidate;
end;
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
const
MyRowColors : array[Boolean] of TColor = (clLime, clMoneyGreen);
var
RowNo : Integer;
OddRow : Boolean;
S : string;
begin
if Sender is TDBGrid then begin
with (Sender as TDBGrid) do begin
if (gdSelected in State) then begin
// Farbe für die Zelle mit dem Focus
// color of the focused row
Canvas.Brush.Color := clblue;
end
else begin
// count := trunc((Sender as TDBGrid).Height div (Rect.Bottom - Rect.Top));
// RowNo := (Sender as TDBGrid).Height div Rect.Top;
RowNo := Rect.Top div (Rect.Bottom - Rect.Top);
OddRow := Odd(RowNo);
Canvas.Brush.Color := MyRowColors[OddRow];
// Font-Farbe immer schwarz
// font color always black
Canvas.Font.Color := clBlack;
Canvas.FillRect(Rect);
// Denn Text in der Zelle ausgeben
// manualy output the text
if Column.Field <> nil then begin
S := Column.Field.AsString;
Canvas.TextOut(Rect.Left + 2, Rect.Top + 1, S);
// Canvas.TextOut(Rect.Left + 2, Rect.Top + 1, 'Column.Field.AsString');
end;
end;
end
end;
end;
I am using the FireMonkey Grid control but have an on-going issue in trying to right align a column. From other users postings, I have managed to create a new TColumn type, apply a style to this (text as HorzAlign=taTrailing) and in theory - thought that this would be solution. The values are provided by the OnGetValue function to the Grid control.
The problem is however that although at first it looks OK, if you scroll the bar/mouse wheel etc. the new TColumn type column does not appear to refresh correctly using the method/code below. It could be a bug/feature of the Grid (or the way I am doing it). I have tried .ReAlign etc...; but to no avail. The only way to get the grid back in line is do a column resize for example - which then redraws correctly?
The code below shows that it is a simple TGrid, with 2 cols, 1 the standard StringColumn and 1 my new StringColNum (wuth right alignment applied). - Any help appreciated as this one is a basic requirement of any grid work.
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Objects, FMX.Grid,
FMX.Layouts, FMX.Edit;
type
TForm1 = class(TForm)
Grid1: TGrid;
Button1: TButton;
StyleBook1: TStyleBook;
procedure Grid1GetValue(Sender: TObject; const Col, Row: Integer;
var Value: Variant);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TStringColNum = class(TStringColumn)
private
function CreateCellControl: TStyledControl; override;
public
constructor Create(AOwner: TComponent); override;
published
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
constructor TStringColNum.Create(AOwner: TComponent);
begin
inherited;
end;
function TStringColNum.CreateCellControl: TStyledControl;
var
t:TEdit;
begin
Result:=TStringColNum.Create(Self);
Result.StyleLookup := 'textrightalign';
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Grid1.AddObject(TStringColumn.Create(Self));
Grid1.AddObject(TStringColNum.Create(Self)); // Right Aligned column?
Grid1.RowCount:=5000;
Grid1.ShowScrollBars:=True;
end;
procedure TForm1.Grid1GetValue(Sender: TObject; const Col, Row: Integer;
var Value: Variant);
var
cell: TStyledControl;
t: TText;
begin
if Col=0 then
Value:='Row '+IntToStr(Row);;
if Col=1 then
begin
cell := Grid1.Columns[Col].CellControlByRow(Row);
if Assigned(cell) then
begin
t := (Cell.FindStyleResource('text') as TText);
if Assigned(t) then
t.Text:='Row '+IntToStr(Row);
end;
end;
end;
end.
Kind regards. Ian.
All of which reminds me that I still haven't written my blog post about this.
Anyway, a grid cell can be any descendant of TStyledControl (basically any control). The default for a text cell is TTextCell, which is simply a TEdit. Being a TEdit means changing the alignment is really easy: just change the TextAlign property. No need to mess with styles (unless you really want to).
Your column needs to create your cells in the CreateCellControl method. You're actually creating an instance of your column which is your main problem.
You don't need the Create method for your column (it's doing nothing), so delete it (unless you need it for something else) and amend your CreateCellControl.
function TStringColNum.CreateCellControl: TStyledControl;
begin
Result:=inherited;
TTextCell(Result).TextAlign := taTrailing;
end;
Finally, your GetValue event handler needs do nothing more than return the value:
procedure TForm1.Grid1GetValue(Sender: TObject; const Col, Row: Integer;
var Value: Variant);
begin
if Col=0 then
Value:='Row '+IntToStr(Row);
if Col=1 then
Value := 'Row '+IntToStr(Row);
end;
I think it is a laziness of Embarcadero.
adding/modifying 3 lines in FMX.Grid.pas solves this problem.
instead of modifiying original FMX.Grid pas, I recommend copying original FMX.Grid pas to your Project directory, including in your Project (add to Project) and adding/modifiying following lines.
TColumn = class(TStyledControl)
private const
HorzTextMargin = 2;
VertTextMargin = 1;
private
FReadOnly: Boolean;
FHorizontalAlign:TTextAlign;//Add this Line *********
FEditMode: Integer;
FApplyImmediately: boolean;
...
...
procedure UpdateCell(ARow: Integer);
published
property HorizontalAlign: TTextAlign read FHorizontalAlign write FHorizontalAlign;//add this line *******
property Align;
property ClipChildren default False;
procedure TColumn.DefaultDrawCell(const Canvas: TCanvas; const Bounds: TRectF; const Row: Integer;
const Value: TValue; const State: TGridDrawStates);
var
R: TRectF;
Layout: TTextLayout;
LocalRow: Integer;
begin
if FDrawable <> nil then
FDrawable.DrawCell(Canvas, Bounds, Row, Value, State)
else
...
...
Layout.Opacity := AbsoluteOpacity;
(*remark this line *****************
Layout.HorizontalAlign := Grid.TextSettingsControl.ResultingTextSettings.HorzAlign;
*)
Layout.HorizontalAlign := HorizontalAlign;//add this line *****
finally you can set the new property in your Project. e.g:
MyColumn.HorizontalAlign:=TTextAlign.taCenter;
Descending columns does not work well with livebindings as the bindmanager creates the columns so you have to mess with descending that. Neither elegant nor practical in my view.
Simply align your cells in the grid OnPainting event.
I := Col;
for J := 0 to Grid1.RowCount - 1 do
begin
T := TTextCell(Grid1.Columns[I].Children[J]);
T.TextAlign := TTextAlign.taTrailing;
end;
If you use livebindings when you have less chance to customize the column class which is being created, but you can create helpers for Column which sets some attributes of individual cell controls. Not too elegant but simple and works:
unit GridColumnHelper;
interface
uses
Fmx.Types, Fmx.Controls, Fmx.Grid, Fmx.Edit;
type
TGridColumnHelper = class helper for TColumn
public
procedure SetEditMaxLength(aValue: Integer);
procedure SetEditTextAlign(aValue: TTextAlign);
end;
implementation
{ TGridColumnHelper }
procedure TGridColumnHelper.SetEditMaxLength(aValue: Integer);
var
lControl: TStyledControl;
begin
for lControl in FCellControls do
begin
if lControl is TEdit then
(lControl as TEdit).MaxLength := aValue;
end;
end;
procedure TGridColumnHelper.SetEditTextAlign(aValue: TTextAlign);
var
lControl: TStyledControl;
begin
for lControl in FCellControls do
begin
if lControl is TEdit then
(lControl as TEdit).TextAlign := aValue;
end;
end;
end.
After the binding has filled the grid, you can call the helpers:
MyGrid.Columns[0].SetEditTextAlign(TTextAlign.taTrailing);
MyGrid.Columns[1].SetEditMaxLength(15);
Solution of "suat dmk" is working fine you have to recompile Fmx.Bind.DBLinks.pas and Fmx.Bind.Editors.pas if you are gonna use DB links.
After that, you simply put in OnPainting event:
SGrid1.ColumnByIndex(1).HorizontalAlign := TTextAlign.Leading;
another solution:
Grid1.ApplyStyleLookup();
MyCol1.DefaultTextSettings.HorzAlign:=TTextAlign.taCenter;