in XE5 I have made application where by default is loaded small txt file with multiple lines using OnShow function.
Also there is 1 TEdit1 field with default value=300 and TButton to save file in defined directory.
How would be possible to:
1.) using value in TEdit1 field to get that amount of files in that specified dir, value could be changed if needed;
2.) all generated files should be like: 1.txt, 2.txt, 3.txt... etc.
Now buttons funcion is:
procedure TForm1.GenerateClick(Sender: TObject);
var
dirName : String;
begin
// Create a new directory
dirName := 'gen';
if DirectoryExists(dirName)
then
Memo1.Lines.SaveToFile('gen\default.txt')
else
CreateDir(dirName);
Memo1.Lines.SaveToFile('gen\default.txt');
end;
Best regards,
G
the whole working code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IOUtils, Vcl.ComCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
SaveFile: TButton;
Generate: TButton;
Memo1: TMemo;
procedure LoadFile(Sender: TObject);
procedure SaveFileClick(Sender: TObject);
procedure GenerateClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.LoadFile(Sender: TObject);
begin
if FileExists('template.txt')then begin
Memo1.Lines.LoadFromFile('template.txt');
end
else
ShowMessage('Cant find template.txt, add text and use SAVE button !!!');
end;
procedure TForm1.SaveFileClick(Sender: TObject);
begin
Memo1.Lines.SaveToFile('template.txt');
end;
procedure TForm1.GenerateClick(Sender: TObject);
var
dirName, fName : String;
i, max: integer;
begin
// Create a new directory
dirName := 'gen';
if NOT DirectoryExists(dirName) then
CreateDir(dirName);
fName := Edit1.Text;
max := StrToInt(fName);
for i := 1 to max do begin
fName := dirName + '\'+ IntToStr(i) + '.txt';
Memo1.Lines.SaveToFile( fName );
end;
end;
end.
Thank you :)
The very idea to store data in visual components is somewhat smelly. But if you insist - then you can just store integer values in .TAG property. But okay, learn few casual functions below and learn begin and end keywords:
Update: converting relative name to fully qualified for ForceDirectories. Used functions:
http://docwiki.embarcadero.com/Libraries/XE2/en/System.SysUtils.ForceDirectories
http://docwiki.embarcadero.com/Libraries/XE2/en/System.SysUtils.ExpandUNCFileName
http://docwiki.embarcadero.com/Libraries/XE2/en/System.SysUtils.GetCurrentDir
http://docwiki.embarcadero.com/Libraries/XE2/en/System.SysUtils.IncludeTrailingPathDelimiter
The code with using Delphi-provided ready-made function then becomes:
procedure TForm1.GenerateClick(Sender: TObject);
var
dirName, fName : String;
i, max: integer;
begin
// Create a new directory
dirName := 'gen';
dirName := ExpandUNCFileName(dirName);
// converting possible relative path to absolute
dirName := IncludeTrailingPathDelimiter(GetCurrentDir) + dirName;
// yet another way to do the same, as above
// GCD function would return paths like "C:\" or like "C:\Users\Name\Documents"
// so we don't know in advance if there would be slash at the end or not
ForceDirectories(dirName);
fName := EditField.Text;
max := StrToInt(fName);
// even better: max := EditField.Tag; and change TAG property, not TEXT in IDE
for i := 1 to max do begin
fName := dirName + PathDelimiter + IntToStr(i) + '.txt';
MemoField.Lines.SaveToFile( fName );
end;
end;
One can also call TDirectory.CreateDirectory(dirName); instead of ForceDirectories but I cannot check now if the former works with relative paths or also requires path expansion before being called. If it can - then the non-changed dirName would be valid parameter to call the function directly.
http://docwiki.embarcadero.com/Libraries/XE2/en/System.IOUtils.TDirectory.CreateDirectory
You original function has a weird, broken structure actually. Double-save. I make below a proper structure of your original code for easy reading:
procedure TForm1.GenerateClick(Sender: TObject);
var
dirName : String;
begin
// Create a new directory
dirName := 'gen';
if DirectoryExists(dirName)
then
Memo1.Lines.SaveToFile('gen\default.txt')
else
CreateDir(dirName);
Memo1.Lines.SaveToFile('gen\default.txt');
end;
Related
Am trying to launch quake from its directory using code below but the pathname is more then 50+ chars so its not working due to limits is there anyway to do larger pathnames?
Pathname example
C:\Users\USERNAME\AppData\Roaming\Gaming\Toys\Q2\quake2.exe
procedure TAutoLauncher.OKBtnClick(Sender: TObject);
var
parameters : string;
toydir : string;
begin
if FileFound then
begin
ToyDir := ExtractFilePath(ExtractFileDir(Application.ExeName)) + 'Toys\';
FolderNameEdit.Text := ToyDir + 'Q2\quake2.exe';
//
parameters := Trim('');
case ModeBox.ItemIndex of
0: parameters := ' +connect '+ HostEdit.Text +':'+ PortEdit.Text +' +password '+ PasswordEdit.Text;
1: parameters := ' +set game ctf';
2: parameters := ' +set game rogue';
3: parameters := ' +set game xatrix';
end;
//
ShellExecute(handle, 'open', PChar(FolderNameEdit.Text), PChar(parameters), nil, SW_SHOWNORMAL);
end else
begin
Application.MessageBox('Quake2 files not found.', 'Game Not Found', MB_OK OR MB_ICONQUESTION);
end;
end;
Thanks
ShellExecute does not contain such a limitation; the limit is the INTERNET_MAX_URL_LENGTH (around 2048) unless you're running on windows 95 (in which case it's MAX_PATH), according to Raymond Chen of Microsoft.
Here's a sample XE8 application compiled as a 32-bit app and run on Windows 10 to demonstrate. (And, even though ShellExecute is the wrong way to launch an executable as Remy said, it also shows you the correct way to use it and test the return value for errors.)
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
ShellAPI;
procedure TForm1.Button1Click(Sender: TObject);
var
FilePath: string;
Res: Integer;
begin
FilePath := 'C:\Users\Ken\Documents\Embarcadero\Studio\Projects\Seattle\Android\AndroidBase\Debug\Styles.xml';
// This works, using the long (96 character) string as the parameters
Res := ShellExecute(0, 'open', 'notepad.exe', PChar(FilePath), nil, SW_NORMAL);
if Res < 32 then
RaiseLastOSError(GetLastError);
// This works, using the long (96 character) string as the filename
Res := ShellExecute(0, 'open', PChar(FilePath), nil, nil, SW_NORMAL);
if Res < 32 then
RaiseLastOSError(GetLastError);
end;
end.
It appears from your comments above that it is Quake itself that is generating the error. In that case, the issue is not a programming issue, but something you should address with the software vendor.
I аm trying to get the enumeration name value using RTTI.
My objective is to get the corresponding enumerate name value in Enum1(Tsex) from the selected enumerate name value in Enum2(iterator) using a string value.
Here is the code that I have implemented. I am using Delphi 7.
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs,typinfo;
type
Tsex = (homme,femme);
iterator = (H,F);
TForm1 = class(TForm)
procedure FormShow(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormShow(Sender: TObject);
var
i : integer;
OT: Tsex;
FT: iterator;
begin
i:=0;
OT := Low(Tsex);
for FT := Low(iterator) to High(iterator) do
if GetEnumName(TypeInfo(iterator), Ord(FT)) = 'F' then
begin
showmessage(GetEnumName(TypeInfo(Tsex), Ord(OT)));
end;
i:=i+1;
OT:=Succ(OT);
end;
When I use H as a string I get homme, but when I use F I also get homme. But it needs to be femme.
Problem:
The problem in your code is that you are missing a begin after for, and this causes increment of i and assignment of OT to happen after the iteration is complete.
What you need to change is:
var
i : integer;
OT: Tsex;
FT: iterator;
begin
i:=0;
OT := Low(Tsex);
for FT := Low(iterator) to High(iterator) do
begin // <- Add begin here
if GetEnumName(TypeInfo(iterator), Ord(FT)) = 'F' then
begin
showmessage(GetEnumName(TypeInfo(Tsex), Ord(OT)));
end;
i:=i+1;
OT:=Succ(OT);
end;
end; // <- Add end; here
Alternative solutions:
As David has pointed out, it is better to use an array to map another set of values to your enum. Like this:
type
TSex = (homme, femme);
const
SexDBValues: array [TSex] of string =
('H', 'F');
type
TForm1 = class(TForm)
procedure FormShow(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function GetMyEnumValue(const aDBValue: string): TSex;
var
value: TSex;
begin
for value := Low(TSex) to High(TSex) do
begin
if SameText(SexDBValues[value], aDBValue) then
begin
Result := value;
Exit;
end;
end;
end;
procedure TForm1.FormShow(Sender: TObject);
var
value: TSex;
begin
value := GetMyEnumValue('H');
ShowMessage(GetEnumName(TypeInfo(TSex), Ord(value)));
end;
And when your enum type contains only two values, and is unlikely to have additional values in future, you can just use good old if-else operator:
function GetMyEnumValue(const aDBValue: string): TSex;
begin
if SameText(aDBValue, 'F') then
begin
Result := femme;
end else
begin
Result := homme;
end;
end;
In few words, avoid overengineering problems.
Note: We are using string to store the character value and SameText to compare it, as it compares text case-insensitively. Plus, it allows you to compare text of multiple characters, if in future you change your mind on how values are stored in DB.
Advice:
I would also recommend you to consult with Delphi Coding Style Guide.
It might seem unrelated to problem, but following good practice on indentation helps to avoid such problems.
Guidelines on naming types and variables are also important. They will similarly save you in other situations.
So, lately we (me and my coworkers) have been chatting about migrating to FireDac, we are currently using IBO and DBX, but mostly IBO. And then we decided to take everything from IBO to FireDac, but entering in every form, changing every IBOQuery, adding all fields, settings all the display format, etc, etc, etc, would take too much time, so we decided to make a component do it, seemed like an easy task, but I just started and I'm already stuck in something that seems simple, but that I never came across before. First let's look at the component code:
unit UMyComponent;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IB_Components, IB_Access,
IBODataset, Vcl.StdCtrls, Vcl.Buttons, Vcl.Grids, Vcl.DBGrids, Data.DB,
uADStanIntf, uADStanOption, uADStanParam, uADStanError,
uADDatSManager, uADPhysIntf, uADDAptIntf, uADStanAsync, uADDAptManager,
uADCompDataSet, uADCompClient;
type
TMyComponent = class(TComponent)
private
FADConnection: TADConnection;
FConverter: String;
procedure Iniciar;
procedure SetADConnection(const Value: TADConnection);
procedure SetConverter(const Value: String);
published
property Converter: String read FConverter write SetConverter;
property ADConnection: TADConnection read FADConnection write SetADConnection;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('MyComponents', [TMyComponent]);
end;
{ TMyComponent }
procedure TMyComponent.Iniciar;
var
Form: TForm;
IBOQuery: TIBOQuery;
i: Integer;
procedure _ConverterIBOQuery;
var
ADQuery: TADQuery;
qName: String;
begin
qName := IBOQuery.Name;
if qName.Contains('OLD_') then
Exit;
IBOQuery.Name := 'OLD_'+ qName;
if (FindComponent(qName) = nil) then
begin
ADQuery := TADQuery.Create(Form);
ADQuery.Name := qName;
ADQuery.Connection := FADConnection;
ADQuery.SQL := IBOQuery.SQL;
{
I need to add the fields here, but I need them having a reference,
like the ones you Right Click > Fields Editor > Add All Fields (CTRL + F)
because in the final form of this component, it won't rename the old query
with an 'OLD_' prefix, it will destroy it, and the fields will be gone too,
so I need to add them (having the reference) in order to not rewrite any of my code
}
end;
end;
begin
if Owner is TForm then
Form := TForm(Owner);
if Assigned(Form) then
begin
for i := 0 to (Form.ComponentCount -1) do
{
I know it will stop in the first query it come across,
but I'm trying to full convert only one to know if it's actually possible
}
if (Form.Components[i] is TIBOQuery) then
begin
IBOQuery := TIBOQuery(Form.Components[i]);
Break;
end;
if Assigned(IBOQuery) then
_ConverterIBOQuery;
end;
end;
procedure TMyComponent.SetConverter(const Value: String);
begin
FConverter := UpperCase(Value[1]);
if (FConverter = 'S') then
Iniciar;
FConverter := '';
end;
procedure TMyComponent.SetADConnection(const Value: TADConnection);
begin
FADConnection := Value;
end;
end.
I already tried some of methods found on the internet, such as:
Creating a variable of TField
Using FieldDefs/FieldDefList, updating them and creating the fields
"Hacking" the ADQuery with a "fake" class in order to use the
CreateFields procedure
And none of them did what I was expecting, so I'm questioning
Can I create the field references via code? And, if it's possible, how?
And with references I mean, for example, you have IBOQuery1, and the SQL is
SELECT NAME
FROM COUNTRY
After that, you go to the Fields Editor > Add All Fields (CTRL + F), and then you have the reference IBOQuery1NAME, which is a TStringField and you can just call IBOQuery1NAME.AsString instead of IBOQuery1.FieldByName('NAME').AsString
TL;DR
Trying to create a component that migrate a IBOQuery to ADQuery, but I can't create the references
After many attempts and research, I found an old question with a problem similar to mine, and happily there was a answer with exactly what I wanted
How to add a field programatically to a TAdoTable in Delphi
The answer was provided by the user: Мסž
procedure AddAllFields(DataSet: TDataset);
var
FieldsList: TStringList;
FieldName: WideString;
Field: TField;
WasActive: boolean;
FieldDef: TFieldDef;
i: Integer;
begin
WasActive := DataSet.Active;
if WasActive then
DataSet.Active := False;
try
FieldsList := TStringList.Create;
try
DataSet.FieldDefs.Update;
// make a list of all the field names that aren't already on the DataSet
for i := 0 to DataSet.FieldDefList.Count - 1 do
with DataSet.FieldDefList[i] do
if (FieldClass <> nil) and not(faHiddenCol in Attributes) then
begin
FieldName := DataSet.FieldDefList.Strings[i];
Field := DataSet.FindField(FieldName);
if (Field = nil) or (Field.Owner <> DataSet.Owner) then
FieldsList.Add(FieldName);
end;
// add those fields to the dataset
for i := 0 to FieldsList.Count - 1 do
begin
FieldDef := DataSet.FieldDefList.FieldByName(FieldName);
Field := FieldDef.CreateField(DataSet.Owner, nil, FieldName, False);
try
Field.name := FieldName + IntToStr(random(MaxInt)); // make the name unique
except
Field.Free;
raise ;
end;
end;
finally
FieldsList.Free;
end;
finally
if WasActive then
DataSet.Active := true;
end;
end;
I have a problem while loading procedures from a dll, either when loading it dynamically or statically. When I put procedures from dll to my unit, everything works fine. When I try to do it with dll it gives me
First chance exception at $00526399. Exception class $C0000005 with message 'access violation at 0x00526399: read of address 0x00000390'. Process Project1.exe (21988)
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls,Unit2;
type
TForm1 = class(TForm)
ListView1: TListView;
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
Edit5: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Refresh;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
type
plist = ^element;
element = record
artist,title,genre: string[20];
year,grade: integer;
wsk: plist;
end;
database = file of element;
var
base: database;
first: plist;
handler: HModule;
{$R *.dfm}
procedure TForm1.Refresh();
var
current: plist;
begin
ListView1.Clear;
current:= first;
while current<>nil do
begin
with ListView1.Items.Add do
begin
Caption:=current^.artist;
SubItems.Add(current^.title);
SubItems.Add(current^.genre);
SubItems.Add(IntToStr(current^.year));
SubItems.Add(IntToStr(current^.grade));
end;
current:=current^.wsk;
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var Save: procedure;
begin
handler:=LoadLibrary('lib.dll');
try
#Save:=GetProcAddress(handler, PChar(2));
if #Save = nil then raise Exception.Create('Load nie dziala');
Save();
finally
FreeLibrary(handler);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Load: procedure;
begin
handler:=LoadLibrary('lib.dll');
try
#Load:=GetProcAddress(handler, PChar(1));
if #Load = nil then raise Exception.Create('Load nie dziala');
Load();
finally
FreeLibrary(handler);
end;
Refresh();
end;
procedure TForm1.Button1Click(Sender: TObject);
var
el: element;
Add: procedure(el:element);
begin
el.artist:=Edit1.Text;
el.title:=Edit2.Text;
el.genre:=Edit3.Text;
el.year:=StrToInt(Edit4.Text);
el.grade:=StrToInt(Edit5.Text);
handler:=LoadLibrary('lib.dll');
try
#Add:=GetProcAddress(handler, PChar(3));
if #Add = nil then raise Exception.Create('Load nie dziala');
Add(el);
finally
FreeLibrary(handler);
Refresh();
{Form2:=TForm2.Create(Form1);
Form2.ShowModal;
Form2.Free;}
end;
end;
end.
The dll file looks like this:
library lib;
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
uses
System.SysUtils,
System.Classes;
{$R *.res}
type plist = ^element;
element = record
artist,title,genre:string[20];
year,grade:integer;
wsk: plist;
end;
database = file of element;
var
first: plist;
base: database;
procedure add(el: element); stdcall;
var current,tmp: plist;
begin
New(current);
current^ := el;
current^.wsk := nil;
if first = nil then
begin
first:=current;
end else
begin
tmp:=first;
while tmp^.wsk<>nil do
begin
tmp:=tmp^.wsk;
end;
tmp^.wsk:=current;
end;
end;
procedure load();stdcall;
var
el: element;
i: integer;
begin
AssignFile(base, 'baza.dat');
if not FileExists('baza.dat') then
begin
Rewrite(base);
end else
begin
Reset(base);
for i := 0 to FileSize(base)-1 do
begin
read(base, el);
add(el);
end;
end;
CloseFile(base);
end;
procedure save();stdcall;
var
current: plist;
el: element;
begin
AssignFile(base, 'baza.dat');
Rewrite(base);
current:=first;
while current<>nil do
begin
el:=current^;
el.wsk:=nil;
write(base, el);
current:= current^.wsk;
end;
end;
exports
add index 1,
load index 2,
save index 3;
begin
end.
It also shows me an error:
Expected ';' but received and identifier 'index' at line 91
But exports are done like I red on web.
The obvious errors are:
You don't perform much error checking. You assume that the calls to LoadLibrary always succeed.
The calling conventions don't match. You use stdcall in the DLL and register in the executable.
The ordinals don't match. In the DLL it is add (1), load (2) and save (3). In the executable you have add (3), load (1) and save (2).
You load and unload the DLL every time you call functions from the DLL. That means that the global variables in the DLL that hold your state are lost each time the DLL is unloaded.
Frankly this code is a real mess. I suggest that you do the following:
Switch to load time linking using the function names rather than ordinals. This means to use the external keyword in the executable. This will greatly simplify your code by removing all those calls to LoadLibrary, GetProcAddress etc. If runtime linking is needed, you can add it later using the delayed keyword.
Stop using global state in the DLL and instead pass information back and forth between modules. Remove all global variables. But make sure you don't pass Delphi objects back and forth.
Use PChar rather than short strings across the module boundary.
Stop using linked lists and dynamic allocation. That's hard to get right. Use TList<T> in the DLL to store the list of elements.
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;