Show multiple Columns contents in a ComboBox - delphi

I want to show FirstName and LastName in a Combobox drop down.
The Problem is That I should work On 2 fields existing in a Table.
I checked the TDBlLookUpComboBox but the ListFields properties doesn't work for me in Delphi Xe.
I set The properties of TDBlLookUpComboBox to
DataSource :my datasource
ListFields : SUR_NAME; FIRST_NAME
Now, I am doing it by a basic way :
nameClient := Concat( sqlqry1.Fields.FieldByName('FIRST_NAME').AsString,' ',
sqlqry1.Fields.FieldByName('SUR_NAME').AsString);
cbbClient.Items.Add(nameClient);

Change the input
Just change the source data going into the DBCombo by putting in a query:
select concat(SUR_NAME,' ',FIRST_NAME) as NAME, * from mytable;
Now you can display the data in your combo box.
Make the output look nice in the combobox
Obviously you'll be using a TDBILookUpComboBox but everything else is the same.
In order to make multiple columns in the drop down you'll have to do your own drawing.
This can be done by changing the style to csOwnerDrawFixed and assigning the OnDrawItem event.
See the sample code below:
unit Unit18;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm18 = class(TForm)
ComboBox1: TComboBox;
procedure ComboBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
private
public
end;
var
Form18: TForm18;
implementation
uses
System.Types,
StrUtils;
{$R *.dfm}
procedure TForm18.ComboBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
var
Columns: TStringDynArray;
ColCount: Integer;
ItemText: string;
DC: HDC;
DrawRect: TRect;
Middle: integer;
begin
ItemText:= ComboBox1.Items[index];
Columns:= SplitString(ItemText, '|');
ColCount:= Length(Columns);
// For Demo purposes only the first 2 columns are displayed.
DC:= ComboBox1.Canvas.Handle;
Combobox1.Canvas.FillRect(Rect);
Middle:= Rect.Left + Rect.Width div 2;
Combobox1.Canvas.MoveTo(Middle, Rect.Top);
Combobox1.Canvas.LineTo(Middle, Rect.Bottom);
if ColCount > 0 then begin
DrawRect:= Rect;
OffsetRect(DrawRect,1,0);
DrawRect.Right:= DrawRect.Right - DrawRect.Width div 2;
DrawText(DC, Columns[0], Length(Columns[0]), DrawRect, 0);
end;
if ColCount > 1 then begin
DrawRect:= Rect;
OffsetRect(DrawRect,1,0);
DrawRect.Left:= DrawRect.Left + DrawRect.Width div 2;
DrawText(DC, Columns[1], Length(Columns[1]), DrawRect, 0);
end;
end;
end.
Put the following text in the Items:
test | test
line2 | part2
line 4 | part3
line 6
And this is what will be displayed:

Related

How can I determine the codepage of the selected keyboard language in Win10?

I need for some reason the codepage of the language set by the currently selected keyboard layout in the current process. (I use Win10 with per app language settings)
getThreadLocale does not change when UI language changes. It gives back the default locale of the process.
getProcessInformation/getThreadInformation does not contain any information about the current language/locale.
I think the chain of the needed information is:
selected language => matching locale => codepage
if I have the current locale id (matching to the selected language) then I can fetch its codepage by:
getLocaleInfoW( idLocale, LOCALE_IDEFAULTANSICODEPAGE, buff, buffSize );
Is(Are) there any winapi call(s) to get the information described above?
The TLabel caption sets to the CodePage associated with the current keyboard language by the TButton.OnClick event handler.
unit Unit3;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm3 = class(TForm)
Label1: TLabel;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure TForm3.Button1Click(Sender: TObject);
var
tid : word;
lid : word;
ndxLocale, buffSize : integer;
localeName : string;
buff : pchar;
begin
tid := getCurrentThreadID;
lid := getKeyboardLayout( tid );
ndxLocale := languages.IndexOf( lid );
localeName := languages.LocaleName[ndxLocale];
buffSize := getLocaleInfoEx( pchar( localeName ), LOCALE_IDEFAULTANSICODEPAGE, NIL, 0 );
getMem( buff, buffSize*sizeOf(char) );
try
getLocaleInfoEx( pchar( localeName ), LOCALE_IDEFAULTANSICODEPAGE, buff, buffSize );
label1.caption := strPas( buff );
finally
freeMem( buff );
end;
end;
GetACP() returns "ansi" code page...lol, not really ansi, but that's what windows calls it. Can also use GetCPInfo() to get additional information after you call GetACP(). Things get trickier for Japanese, Chinese, and other far east languages that use double byte character set. I still work on an application that is MBCS. Would be nice if we could convert to Unicode, but it's not happening and it won't be my problem soon.

Another Delphi Invalid Pointer Operation

This VCL Form program generates the Invalid Pointer Operation notice:
Uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls,
DcadMenu_u;
type
TForm1 = class(TForm)
MenuTestRichEdit: TRichEdit;
LoadButton: TButton;
procedure ButtonLoadClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.ButtonLoadClick(Sender: TObject);
var
menu : TDcadMenu;
item1, item2 : TDcadMenuItem;
strlist :tstringlist;
i : integer;
begin
menu := tDcadMenu.Create();
item1 := TDcadMenuItem.create ('Option1', 'Do Option1', false, false, true);
menu.add (item1);
item2 := TDcadMenuItem.create ('Option2', 'Do Option2', false, false, true);
menu.add (item2);
strlist := tstringlist.Create;
Try
For i := 0 to Menu.Count - 1 DO
begin
item1 := menu.Items[i];
strlist.Add (Item1.lblset + ' | ' + Item1.lblmsg );
end;
Form1.MenuTestRichEdit.Lines := strlist;
finally
item1.free;
item2.Free;
menu.free;
strlist.Free;
end;
end;
The code works fine and generates the item list in the Richedit component. I suspect I am freeing an object that is already being handled, but not clear on what the cause is specifically. Can someone explain this?
We can't see the implementation of TDcadMenu, but normally adding items to a class gives the ownership of the items to that class, so there is no need to free the items outside of the class. As #Remy comments, it is normally safe to free them before before freeing the menu object, though.
In your code you are reassigning item1, and when freeing the items, Item1 and Item2 both shares the same instance as menu.Items[1]. This means that you have a double free, which gives your invalid pointer notice.
item1.free;
item2.Free; // <- Double free of same instance

Delphi Firemonkey: Creating Cube on a 3D viewport at runtime, from a different unit

I'm designing 3D minesweeper. I would like to create a class to generate the 3D Cube (which will be composed of multiple TCubes, in a cube shape), which will be in a seperate class and unit to the 3D Viewport. I need to make the Cube to runtime. I am using a Multidimensional array (Array of Array of Array of TCube). This is the code I currently have which attempts to do so
Main Unit:
unit mineMainForm;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Rtti, System.Classes,
System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.Types3D,
FMX.Objects3D, mineControl;
type
TForm2 = class(TForm3D)
procedure Form3DCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
Controller : TController;
implementation
{$R *.fmx}
procedure TForm2.Form3DCreate(Sender: TObject);
begin
Controller.create(Form2,10);
end;
end.
Unit in which the issue lies:
unit mineControl;
interface
uses
FMX.Forms, FMX.Objects3D, sysutils;
Type
tController = class
private
cubeArray : Array[1..10,1..10,1..10] of TCube;
public
constructor create(Form : TForm3D; cubeCount :integer);
end;
implementation
{ tController }
constructor tController.create(Form: TForm3D; cubeCount: integer); //cubeCount Max 10, min 1
var
x, y, z : Integer;
begin
for x := 1 to cubeCount do
begin
for y := 1 to cubeCount do
begin
for z := 1 to cubeCount do
begin
CubeArray[x,y,z] := TCube.Create(Form);
With CubeArray[x,y,z] do
begin
Visible := True;
Position.X := 0;
Position.Y := 0;
Position.Z := 0;
Parent := Form;
end;
end;
end;
end;
end;
end.
I am receiving an Access violation error where I try to run the create using "mineField" as the AOwner. How could I fix this so it works, or if that would not work, how would I do it?
I am quite new to coding (1 1/2 years of high school) and would appreciate if you assume I know quite little. Thanks a lot.
Error: "Access Violation at address 00A0CFBB in module 'mine3D_p.exe'. Write of Address 00000008"
The Issue is you are calling
Controller.create(Form2,10);
instead of
Controller := TController.create(Form2,10);

Destroy shapes on form close

Currently when i click on a button it will create some shapes on a new form. Once i close the new form how can i destroy the shapes it made.
I can add more info if needed but was hopeing there was a simple way to destroy all TMachine instances when the form closed.
TMachine is a TShape Class
procedure TFLayout1.GetClick(Sender: TObject);
var
azone: string;
adept: string;
machine : TMachine;
begin
fdb.count := 0; //keeps track of number of machines in zone
azone := MyDataModule.fDB.GetZone(Name); //gets name of zone
adept := TButton(Sender).Name; //gets name of dept
fdeptlayout.ListBox1.Clear;
fdeptlayout.show;
with fdeptlayout.ADOQuery1 do
begin
sql.Clear;
sql.BeginUpdate;
sql.Add('SELECT');
sql.Add(' *');
sql.Add('FROM');
sql.Add(' `MList`');
sql.Add('WHERE `Zone` = :myzone ');
sql.Add(' AND `Dept` = :mydept');
sql.EndUpdate;
parameters.ParamByName('myzone').Value := azone;
parameters.ParamByName('mydept').Value := adept;
open;
end;
//gets number of machines in total
while not fdeptlayout.ADOQuery1.Eof do
begin
fdb.count := fdb.count+1;
fdeptlayout.ADOQuery1.Next;
end;
//restarts back at first query
fdeptlayout.ADOQuery1.First;
//clears the last x value
fdb.LastX :=0;
//creates the shape
while not fdeptlayout.ADOQuery1.Eof do
begin
machine := MachineShape.TMachine.Create(self);
machine.Parent := fdeptlayout;
machine.PlaceShape(44,44,'CM402','first','123/33/123');
fdeptlayout.ListBox1.Items.Add(fdeptlayout.ADOQuery1.FieldByName('Name').AsString);
fdeptlayout.ADOQuery1.Next;
end;
end;
TMachine Class
unit MachineShape;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, extctrls,myDataModule,Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TMachine = class(TShape)
private
{ Private declarations }
public
{ Public declarations }
procedure PlaceShape(sizeW,sizeH :integer; name, order,asset : string);
end;
implementation
Procedure TMachine.PlaceShape(sizeW,sizeH :integer; name, order,asset : string);
begin
self.width := sizeW;
self.height := sizeH;
self.top := 136;
self.left := MyDataModule.fDB.LastX +2;//set left
MyDataModule.fDB.lastx := left + sizeW;
showmessage(inttostr(mydatamodule.fDB.LastX));
end;
end.
FDeptLayout
unit DeptLayout;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls,mydatamodule, Vcl.Forms, Vcl.Dialogs, Data.DB, Data.Win.ADODB, Vcl.StdCtrls,
Vcl.ExtCtrls;
type
TfDeptLayout = class(TForm)
ADOQuery1: TADOQuery;
ListBox1: TListBox;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;
var
fDeptLayout: TfDeptLayout;
implementation
{$R *.dfm}
procedure TfDeptLayout.FormClose(Sender: TObject; var Action: TCloseAction);
begin
end;
end.
The shown code is taking advantage of the VCL ownership model and the form will free it for you, as you just pass the form itself as the owner of your components when you create it:
machine := MachineShape.TMachine.Create(self);
as this is called from the TFLayout1 class, when the particular instance of the form is destroying itself, it will free all the owned components.
For a little more info, you can read the article: Owner vs. Parent in Delphi.
Edit
From comments, it resulted you create the TMachine instances on a class different of the form on which you show it, and you don't destroy the form instance when you close it, so, you can reach what you want making this changes:
Make the form in which the shapes are shown the owner, changing your code to create them to this:
//don't use self, now the parent is the instance referenced by fdeptlayout
machine := MachineShape.TMachine.Create(fdeptlayout);
On your Tfdeptlayout class, add a OnClose handler with this code:
begin
for I := ComponentCount - 1 downto 0 do
if Components[I] is TMachine then
Components[I].Free;
end;
That said, you really have to read the documentation and referenced articles to gain some understanding of what's going on behind the scenes in your Delphi application.
You are assigning an Owner to your TMachine objects. The shapes will be freed automatically when the Owner itself is freed.
Assuming TFLayout1 is your Form class, then by default it will not be freed automatically when it is closed. A closed Form is hidden by default so you can re-show when needed. To actually free it on close, you have to either set the Action parameter in the TForm.OnClose event to caFree, or call TForm.Free() directly sometime after the form is closed (such as if you are displaying the Form with ShowModal(), then you can call Free() after ShowModal() exits).
If you want to free the shapes yourself without relying on the behavior of an Owner, then set the Owner to nil when you create the shapes, and store your TMachine pointers in a TList that you can loop through when needed to free each shape, or a TObjectList with its OwnsObjects property set to true that you can Clear() when needed. Such as in the Form's OnClose event.

Firemonkey Grid Control - Aligning a column to the right

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;

Resources