I'm having trouble creating a new procedure for a dynamic button. When the button is clicked, it supposed to send items to a RichEdit.
I have declared all my objects and my procedures.
Here's my code:
type
TfrmSale = class(TForm)
redOutput: TRichEdit;
btnBuy: TButton;
procedure btnBuyClick(Sender: TObject);
private
pnlSale : TPanel;
edtQuantity : TEdit;
rgpOptions : TRadioGroup;
btnSelect : TButton;
procedure btnSelectCick(Sender: TObject);
public
{ Public declarations }
end;
var
frmSale: TfrmSale;
implementation
{$R *.dfm}
procedure TfrmSale.btnBuyClick(Sender: TObject);
begin
pnlSale := TPanel.Create(frmSale);
pnlSale.Parent := frmSale;
pnlSale.Left := 10;
pnlSale.Top := 10;
pnlSale.Width := 350;
pnlSale.Height := 250;
pnlSale.Visible := True;
edtQuantity := TEdit.Create(pnlSale);
edtQuantity.Parent := pnlSale;
edtQuantity.Left := 50;
edtQuantity.Top := 60;
edtQuantity.Width := 80;
edtQuantity.Height := 20;
edtQuantity.Text := 'Quantity';
edtQuantity.Visible := True;
rgpOptions := TRadioGroup.Create(pnlSale);
rgpOptions.Parent := pnlSale;
rgpOptions.Left := 30;
rgpOptions.Top := 100;
rgpOptions.Width := 300;
rgpOptions.Height := 140;
rgpOptions.Visible := True;
rgpOptions.Caption := 'Options';
rgpOptions.Items.Add('Screws 12mm');
rgpOptions.Items.Add('Canned Peaches 250g');
rgpOptions.Items.Add('Tomatoes');
rgpOptions.Items.Add('Spaghetti 500g');
rgpOptions.Items.Add('Twin Flex 5m');
rgpOptions.Items.Add('Clear Glue 250ml');
btnSelect := TButton.Create(frmSale);
btnSelect.Parent := frmSale;
btnSelect.Left := 130;
btnSelect.Top := 260;
btnSelect.Width := 80;
btnSelect.Height := 40;
btnSelect.Caption := 'Select';
end;
procedure TfrmSale.btnSelectClick(Sender: TObject);
case rgpOptions.ItemIndex of
1 := redOutput.Lines.Add('Screws 12mm');
2 := redOutput.Lines.Add('Canned Peaches 250g');
3 := redOutput.Lines.Add('Refil Blue Pen');
4 := redOutput.Lines.Add('Tomatoes');
5 := redOutput.Lines.Add('Spaghetti 500g');
6 := redOutput.Lines.Add('Twin Flex 5m');
7 := redOutput.Lines.Add('Clear Glue 250ml');
end
Please can someone help me make a OnClick procedure.
AS TLama said. If you're going to create your buttons in code,you need to also assign the OnClick handler in code.
type
TfrmSale = class(TForm)
redOutput: TRichEdit;
btnBuy: TButton;
procedure btnBuyClick(Sender: TObject); <<--- link made in designer
private VVVVV-- from here on it's
pnlSale : TPanel; your responsibility
edtQuantity : TEdit;
rgpOptions : TRadioGroup;
btnSelect : TButton;
procedure btnSelectCick(Sender: TObject); <<-- link must be made in code
public
{ Public declarations }
end;
var
frmSale: TfrmSale;
implementation
{$R *.dfm}
procedure TfrmSale.btnBuyClick(Sender: TOBject);
begin
pnlSale := TPanel.Create(frmSale);
pnlSale.Parent := frmSale;
pnlSale.Left := 10;
pnlSale.Top := 10;
pnlSale.Width := 350;
pnlSale.Height := 250;
pnlSale.Visible := True;
edtQuantity := TEdit.Create(pnlSale);
edtQuantity.Parent := pnlSale;
edtQuantity.Left := 50;
edtQuantity.Top := 60;
edtQuantity.Width := 80;
edtQuantity.Height := 20;
edtQuantity.Text := 'Quantity';
edtQuantity.Visible := True;
rgpOptions := TRadioGroup.Create(pnlSale);
rgpOptions.Parent := pnlSale;
rgpOptions.Left := 30;
rgpOptions.Top := 100;
rgpOptions.Width := 300;
rgpOptions.Height := 140;
rgpOptions.Visible := True;
rgpOptions.Caption := 'Options';
rgpOptions.Items.Add('Screws 12mm');
rgpOptions.Items.Add('Canned Peaches 250g');
rgpOptions.Items.Add('Tomatoes');
rgpOptions.Items.Add('Spaghetti 500g');
rgpOptions.Items.Add('Twin Flex 5m');
rgpOptions.Items.Add('Clear Glue 250ml');
btnSelect := TButton.Create(frmSale);
btnSelect.OnClick:= btnSelectCick; <<-- Assign the button handler
btnSelect.Parent := frmSale;
btnSelect.Left := 130;
btnSelect.Top := 260;
btnSelect.Width := 80;
btnSelect.Height := 40;
btnSelect.Caption := 'Select';
end;
procedure TfrmSale.btnSelectClick(Sender: TOBject);
begin
redOutput.Lines.Add(rgpOptions.Lines[rgpOptions.ItemIndex]); <<-- DRY
end;
Note that I've tweaked btnSelectClick so that it reuses the data you've stored into rgpOptions previously.
See also: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself
Related
I'm trying implement IP edit. This my code:
unit Main;
interface
uses
System.SysUtils, System.Classes,
Winapi.Windows, Winapi.Messages, Winapi.CommCtrl,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls, Vcl.ComCtrls;
type
TIpEdit = class(TWinControl)
strict protected
procedure CreateParams(var Params: TCreateParams); override;
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
procedure WMSetFont(var Message: TWMSetFont); message WM_SETFONT;
end;
TMainForm = class(TForm)
Btn1: TButton;
procedure FormCreate(Sender: TObject);
procedure Btn1Click(Sender: TObject);
private
FIpEdit: TIpEdit;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
{ TIPEdit }
procedure TIPEdit.CreateParams(var Params: TCreateParams);
begin
InitCommonControl(ICC_INTERNET_CLASSES);
inherited CreateParams(Params);
CreateSubClass(Params, WC_IPADDRESS);
Params.Style := Params.Style or WS_TABSTOP or WS_CHILD;
end;
procedure TIPEdit.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
inherited;
Message.Result := Message.Result or DLGC_WANTARROWS;
end;
procedure TIPEdit.WMSetFont(var Message: TWMSetFont);
var
LF: LOGFONT;
begin
if GetObject(Message.Font, SizeOf(LF), #LF) <> 0 then
begin
Message.Font := CreateFontIndirect(LF);
inherited;
end;
end;
{ TMainForm }
procedure TMainForm.Btn1Click(Sender: TObject);
begin
FIpEdit.Width := FIpEdit.Width + 100;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FIpEdit := TIpEdit.Create(Self);
FIpEdit.Parent := Self;
FIpEdit.SetBounds(10, 10, 120, 21);
end;
end.
Before Btn1.Click
After Btn1.Click
As a result: Control change own width but not change width internal edits.
I try fix by two ways
Use RecreateWnd. This works, but IMHO its ugly solution.
Fix internal edits width manualy. This works, but it is difficult to
implement, due to the internal work of the control
Maybe I missed something and there is a simpler solution?
Edit:
I tested the RecreateWnd, but it can not be used when using DevExpress layout control. It seems that layout control uses the direct call of the API bypassing the SetBounds method. In this case RecreateWnd can not be using.
The final conclusion is:
RecreateWnd is simple solution with some ristrictions
RepeatUntil answer more difficult, but always working
The control does not provide a mechanism to update its layout after it has been created. Yes you could hack at the internal edit controls, but that is dangerous. You'd be relying on undocumented implementation detail that is subject to change.
In summary, I would say that recreating the window is the best solution available.
You can use TPanel with child TEdit controls to create your own IP Edit, That will give you more control of their property.
This how its look at run time:
I wrote this time ago, its not completed work but may give an idea of what i mean.
unit IPEdit;
interface
uses
System.SysUtils, System.Classes, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Forms,
Vcl.Graphics, System.UITypes;
type
TExitType = (etNone, etNext);
type
TIPEdit = class(TCustomPanel)
private
FPart1 : TEdit;
FPart2 : TEdit;
FPart3 : TEdit;
FPart4 : TEdit;
FSplitter1 : TPanel;
FSplitter2 : TPanel;
FSplitter3 : TPanel;
FRiseErr : Boolean;
FErrMsg : string;
FExitType : TExitType;
FBevelInner : TPanelBevel;
FLeadingzero: Boolean;
procedure SetExitType(Value : TExitType);
procedure SetBevelInner(Value: TPanelBevel);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
protected
procedure EditOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure EditOnChange(Sender: TObject);
procedure OnPanelResize(Sender: TObject);
procedure EditOnExit(Sender: TObject);
published
property ShowError: Boolean read FRiseErr write FRiseErr default False;
property Leadingzero: Boolean read FLeadingzero write FLeadingzero default False;
property ErrorText: string read FErrMsg write FErrMsg;
property ExitType: TExitType read FExitType write SetExitType default etNext;
property BevelInner: TPanelBevel read FBevelInner write SetBevelInner default bvNone;
end;
implementation
const
Msg_Err_Value_Exceeded = 'Value cannot be greater than 255';
SplitterWidth = 5;
{ TIPEdit }
constructor TIPEdit.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
if AOwner is TWinControl then
Parent := TWinControl(AOwner);
{ Main Panle Style }
ParentBackground := False;
BevelKind := bkFlat;
BevelOuter := bvNone;
Color := clWindow;
Height := 25;
Width := 165;
Caption := '';
/////////////////////////////
{ Set Handlers}
OnResize := OnPanelResize;
{ Create child controls }
FPart1 := TEdit.Create(Self);
FPart1.Name := 'IPEditPart1';
FPart1.Visible := False;
FSplitter1 := TPanel.Create(Self);
FSplitter1.Name := 'IPSplitter1';
FSplitter1.Visible:= False;
FPart2 := TEdit.Create(Self);
FPart2.Name := 'IPEditPart2';
FPart2.Visible := False;
FSplitter2 := TPanel.Create(Self);
FSplitter2.Name := 'IPSplitter2';
FSplitter2.Visible:= False;
FPart3 := TEdit.Create(Self);
FPart3.Name := 'IPEditPart3';
FPart3.Visible := False;
FSplitter3 := TPanel.Create(Self);
FSplitter3.Name := 'IPSplitter3';
FSplitter3.Visible:= False;
FPart4 := TEdit.Create(Self);
FPart4.Name := 'IPEditPart4';
FPart4.Visible := False;
FPart1.Align := alLeft;
FSplitter1.Align := alLeft;
FPart2.Align := alLeft;
FSplitter2.Align := alLeft;
FPart3.Align := alLeft;
FSplitter3.Align := alLeft;
FPart4.Align := alLeft;
/////////////////////////////
{ Set Child Style }
// This order is very important //
FPart1.Parent := TWinControl(Self);
FSplitter1.Parent := TWinControl(Self);
FPart2.Parent := TWinControl(Self);
FSplitter2.Parent := TWinControl(Self);
FPart3.Parent := TWinControl(Self);
FSplitter3.Parent := TWinControl(Self);
FPart4.Parent := TWinControl(Self);
FPart1.Visible := True;
FSplitter1.Visible := True;
FPart2.Visible := True;
FSplitter2.Visible := True;
FPart3.Visible := True;
FSplitter3.Visible := True;
FPart4.Visible := True;
//////////////////////////////////
FPart1.Alignment := taCenter;
FPart2.Alignment := taCenter;
FPart3.Alignment := taCenter;
FPart4.Alignment := taCenter;
FPart1.Margins.Left := 0;
FPart2.Margins.Left := 0;
FPart3.Margins.Left := 0;
FPart4.Margins.Left := 0;
FSplitter1.Margins.Left := 0;
FSplitter2.Margins.Left := 0;
FSplitter3.Margins.Left := 0;
FPart1.Margins.Right := 0;
FPart2.Margins.Right := 0;
FPart3.Margins.Right := 0;
FPart4.Margins.Right := 0;
FSplitter1.Margins.Right := 0;
FSplitter2.Margins.Right := 0;
FSplitter3.Margins.Right := 0;
FPart1.AlignWithMargins := True;
FSplitter1.AlignWithMargins := True;
FPart2.AlignWithMargins := True;
FSplitter2.AlignWithMargins := True;
FPart3.AlignWithMargins := True;
FSplitter3.AlignWithMargins := True;
FPart4.AlignWithMargins := True;
FPart1.AutoSize := False;
FPart2.AutoSize := False;
FPart3.AutoSize := False;
FPart4.AutoSize := False;
FPart1.BorderStyle := bsNone;
FPart2.BorderStyle := bsNone;
FPart3.BorderStyle := bsNone;
FPart4.BorderStyle := bsNone;
FPart1.NumbersOnly := True;
FPart2.NumbersOnly := True;
FPart3.NumbersOnly := True;
FPart4.NumbersOnly := True;
FPart1.MaxLength := 3;
FPart2.MaxLength := 3;
FPart3.MaxLength := 3;
FPart4.MaxLength := 3;
FPart1.Width := 36;
FPart2.Width := 36;
FPart3.Width := 36;
FPart4.Width := 36;
FSplitter1.Alignment := taCenter;
FSplitter2.Alignment := taCenter;
FSplitter3.Alignment := taCenter;
FSplitter1.Caption := '.';
FSplitter2.Caption := '.';
FSplitter3.Caption := '.';
FSplitter1.BevelOuter := bvNone;
FSplitter2.BevelOuter := bvNone;
FSplitter3.BevelOuter := bvNone;
FSplitter1.Color := clWindow;
FSplitter2.Color := clWindow;
FSplitter3.Color := clWindow;
FSplitter1.ParentBackground := False;
FSplitter2.ParentBackground := False;
FSplitter3.ParentBackground := False;
FSplitter1.TabStop := False;
FSplitter2.TabStop := False;
FSplitter3.TabStop := False;
FSplitter1.Width := SplitterWidth;
FSplitter2.Width := SplitterWidth;
FSplitter3.Width := SplitterWidth;
FSplitter1.Font.Style := FSplitter1.Font.Style + [fsBold];
FSplitter2.Font.Style := FSplitter2.Font.Style + [fsBold];
FSplitter3.Font.Style := FSplitter3.Font.Style + [fsBold];
//////////////////////////////
{Set Child handlers}
FPart1.OnChange := EditOnChange;
FPart2.OnChange := EditOnChange;
FPart3.OnChange := EditOnChange;
FPart4.OnChange := EditOnChange;
FPart1.OnKeyDown := EditOnKeyDown;
FPart2.OnKeyDown := EditOnKeyDown;
FPart3.OnKeyDown := EditOnKeyDown;
FPart4.OnKeyDown := EditOnKeyDown;
FPart1.OnExit := EditOnExit;
FPart2.OnExit := EditOnExit;
FPart3.OnExit := EditOnExit;
FPart4.OnExit := EditOnExit;
{Set Child control tab order for the handlers work}
FPart1.TabOrder := 0;
FPart2.TabOrder := 1;
FPart3.TabOrder := 2;
FPart4.TabOrder := 3;
FPart1.Text := '1';
FPart2.Text := '2';
FPart3.Text := '3';
FPart4.Text := '4';
FExitType := etNext;
FErrMsg := Msg_Err_Value_Exceeded;
end;
destructor TIPEdit.Destroy;
begin
FPart1.Free;
FPart2.Free;
FPart3.Free;
FPart4.Free;
FSplitter1.Free;
FSplitter2.Free;
FSplitter3.Free;
inherited;
end;
procedure TIPEdit.EditOnChange(Sender: TObject);
var
iValue : Integer;
bValGrater : Boolean;
I: Integer;
begin
if NOT (Sender is TEdit) then Exit;
bValGrater := False;
if TryStrToInt(TEdit(Sender).Text, iValue) then begin
if (iValue > 255) then begin
iValue := 255;
bValGrater := True;
TEdit(Sender).Text := iValue.ToString;
if (FRiseErr and bValGrater) then
raise Exception.Create(FErrMsg);
end;
end;
if Length(TEdit(Sender).Text) = 3 then begin
case FExitType of
etNone: ;
etNext: FindNextControl(TEdit(Sender), True, False, False).SetFocus;
end;
end;
end;
procedure TIPEdit.EditOnKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
const
vkReturn = $0D;
begin
if not (Sender is TEdit) then Exit;
if Key = vkReturn then
FindNextControl(TEdit(Sender), True, False, False).SetFocus;
end;
procedure TIPEdit.OnPanelResize(Sender: TObject);
const
EditCount = 4;
SplitterCount = 3;
var
EditWidth : Integer;
FSplitterWidth : Integer;
EditMargin : Integer;
begin
EditWidth := Trunc(((Width) / EditCount) - (SplitterCount * (SplitterWidth)));
FPart1.Width := EditWidth;
FPart2.Width := EditWidth;
FPart3.Width := EditWidth;
FPart4.Width := EditWidth;
FSplitterWidth := Trunc(( (Width) - ( EditWidth * EditCount) ) / SplitterCount);
FSplitter1.Width := FSplitterWidth;
FSplitter2.Width := FSplitterWidth;
FSplitter3.Width := FSplitterWidth;
{Center edits text vertically == this is a temporary workaround}
FPart1.Margins.Top := 0;
FPart2.Margins.Top := 0;
FPart3.Margins.Top := 0;
FPart4.Margins.Top := 0;
EditMargin := Round( ((Height / 5)));
FPart1.Margins.Top := EditMargin;
FPart2.Margins.Top := EditMargin;
FPart3.Margins.Top := EditMargin;
FPart4.Margins.Top := EditMargin;
FSplitter1.Margins.Top := EditMargin + SplitterWidth; { the +lblWidth to make it lower than the edits}
FSplitter2.Margins.Top := EditMargin + SplitterWidth;
FSplitter3.Margins.Top := EditMargin + SplitterWidth;
end;
procedure TIPEdit.SetBevelInner(Value: TPanelBevel);
begin
TPanel(Self).BevelInner := Value;
end;
procedure TIPEdit.SetExitType(Value: TExitType);
begin
FExitType := Value;
end;
procedure TIPEdit.EditOnExit(Sender: TObject);
var
I: Integer;
begin
if not (Sender IS TEdit) then Exit;
if FLeadingzero then begin
if Length(TEdit(Sender).Text) >= 1 then
for I := Length(TEdit(Sender).Text) to 2 do begin
TEdit(Sender).Text := '0' + TEdit(Sender).Text;
end;
end;
end;
end.
Say that I have five TRectangle objects, and a function is going to pass a parameter in to make one of them blink.
I know how to control one object like the following code:
procedure TForm1.TimerTimer(Sender: TObject);
begin
if rect1.Visible then
rect1.Visible := false
else
rect1.Visible := true;
end;
procedure TForm1.Blink_Square;
begin
Timer := TTimer.Create(nil);
Timer.OnTimer := TimerTimer;
rect1.Fill.Color := TAlphacolors.Red;
rect1.fill.Kind := TBrushKind.bkSolid;
rect1.Stroke.Thickness := 1;
rect1.Stroke.Color := Talphacolors.Darkgray;
Timer.Interval := 500;
Timer.Enabled := True;
end;
But I really wonder if there is a way that I can use the blink square repeatedly like having a procedure as procedure TForm1.Blink_Square(rec_number: integer); And we can call Blink_Square(5); to make rect5 blink.
Thanks in Advance
You can store your objects in an array or list, then use your procedure parameter to index into it.
var
Blinks: array[1..5] of record
Rectangle: TRectangle;
Timer: TTimer;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Blinks[1].Rectangle := Rect1;
Blinks[1].Timer := nil;
Blinks[2].Rectangle := Rect2;
Blinks[2].Timer := nil;
Blinks[3].Rectangle := Rect3;
Blinks[3].Timer := nil;
Blinks[4].Rectangle := Rect4;
Blinks[4].Timer := nil;
Blinks[5].Rectangle := Rect5;
Blinks[5].Timer := nil;
end;
procedure TForm1.TimerTimer(Sender: TObject);
var
Timer: TTimer;
begin
Timer := TTimer(Sender);
Blinks[Timer.Tag].Visible := not Blinks[Timer.Tag].Visible;
end;
procedure TForm1.Blink_Square(Number: Integer);
begin
Blinks[Number].Rectangle.Fill.Color := TAlphacolors.Red;
Blinks[Number].Rectangle.fill.Kind := TBrushKind.bkSolid;
Blinks[Number].Rectangle.Stroke.Thickness := 1;
Blinks[Number].Rectangle.Stroke.Color := Talphacolors.Darkgray;
if Blinks[Number].Timer = nil then
begin
Blinks[Number].Timer := TTimer.Create(Self);
Blinks[Number].Timer.OnTimer := TimerTimer;
Blinks[Number].Timer.Interval := 500;
Blinks[Number].Timer.Tag := Number;
Blinks[Number].Timer.Enabled := True;
end;
end;
Alternatively:
var
Rects: array[1..5] of TRectangle;
procedure TForm1.FormCreate(Sender: TObject);
begin
Rects[1] := Rect1;
Rects[2] := Rect2;
Rects[3] := Rect3;
Rects[4] := Rect4;
Rects[5] := Rect5;
end;
procedure TForm1.TimerTimer(Sender: TObject);
begin
TRectangle(Sender).Visible := not TRectangle(Sender).Visible;
end;
procedure TForm1.Blink_Square(Number: Integer);
var
Rec: TRectangle;
Timer: TTimer;
M: TNotifyEvent;
begin
Rec := Rects[Number];
Rec.Fill.Color := TAlphacolors.Red;
Rec.fill.Kind := TBrushKind.bkSolid;
Rec.Stroke.Thickness := 1;
Rec.Stroke.Color := Talphacolors.Darkgray;
if Rec.Tag = 0 then
begin
M := TimerTimer;
TMethod(M).Data := Rec;
Timer := TTimer.Create(Rec);
Timer.OnTimer := M;
Timer.Interval := 500;
Timer.Enabled := True;
Rec.Tag := NativeInt(Timer);
end;
end;
So I creat some TEdit components like this
var
lb : TLabel;
topLabel, i: Integer;
dbedit : TEdit;
begin
inherited;
topLabel := 40;
i := 0;
lb := TLabel.Create(nil);
lb.Parent := GroupBox2;
lb.Left := 245;
lb.Top := 20;
lb.Caption := 'ASD';
with DataModule.myStoredProc do begin
Close;
ParamByName('#Param1').AsInteger := 1;
ExecProc;
Open;
SetLength(nrPozitiiDinctionar,RecordCount);
First;
while not Eof do begin
lb := TLabel.Create(nil);
lb.Parent := GroupBox2;
lb.Left := 7;
lb.Top := topLabel ;
lb.Caption := FieldByName('X').AsString;
dbedit := TEdit.Create(nil);
dbedit.Name := 'Edit'+IntToStr(FieldByName('Poz').AsInteger);
dbedit.Text := '';
dbedit.Parent := GroupBox2;
dbedit.Height := 21;
dbedit.Width := 40;
dbedit.Left := 240;
dbedit.Top := lb.Top-3 ;
topLabel := topLabel + 30;
nrPozitiiDinctionar[i] := FieldByName('Poz').AsInteger;
i := i + 1;
Next;
end;
end;
end;
Then after the user add his input I run a function with this code
var
IDPoz, I : Integer;
dbedit : TEdit;
pctj,nume : string;
begin
for I := Low(nrPozitiiDinctionar) to High(nrPozitiiDinctionar) do
begin
nume := 'Edit'+IntToStr(nrPozitiiDinctionar[i]);
pctj := TEdit(FindComponent('Edit'+IntToStr(nrPozitiiDinctionar[i]))).Text;
with DateCOFurnizori.spCOFCmzFurnizoriEvaluarePozitii_Edit do begin
ParamByName('#IDEvaluare').AsInteger := StrToInt(Edit1.Text);
ParamByName('#IDPozitie').AsInteger := IDPoz;
ParamByName('#DictionarID').AsInteger := 9103;
ParamByName('#DictionarPozitiiID').AsInteger := nrPozitiiDinctionar[i];
ParamByName('#Punctaj').AsFloat := 1 ;//StrToFloat(pctj) ;
ParamByName('#DataEvaluare').AsDateTime := Now;
ExecProc;
IDPoz := IDPoz + 1;
end;
end;
This is only a portion of the code but this should relate to my problem.
When I use the debugger there is no value in pctj, what am I doing wrong? I try to get the value of the TEdits based on their names. What am I doing wrong with the FindComponent function?
You are not assigning an Owner to the TEdit controls, so that is why FindComponent() cannot find them. Either assign Self as the Owner (since you are calling Self.FindComponent()), or else store the TEdit pointers in a TList or TObjectList that you can loop through when needed.
I'm having problems in my code but can not find the solution, already modified in various ways but no success.
Code:
private
{ Private declarations }
procedure getImgInfo(Sender: TObject; A, B: String);
And:
procedure TfMain.Button1Click(Sender: TObject);
var
i, Idx, Left, Top, Count : integer;
Graph : TGraphic;
Img : TImage;
EdPath, EdFileName : TEdit;
begin
openImg.Execute;
Left := 5;
Top := 5;
Count := 0;
Idx := 0;
for i:=0 to openImg.Files.Count-1 do
begin
try
begin
Graph := TPngImage.Create;
Graph.LoadFromFile(openImg.Files[i]);
EdPath := TEdit.Create(pImgs);
EdPath.Left := Left + 101;
EdPath.Visible := False;
EdPath.Text := ExtractFilePath(openImg.Files[i]);
EdFileName := TEdit.Create(pImgs);
EdFileName.Left := Left + 101;
EdFileName.Visible := False;
EdFileName.Text := ExtractFileName(openImg.Files[i]);
Img := TImage.Create(pImgs);
Img.Parent := pImgs;
Idx := Idx + 1;
Img.Name := 'Img_'+IntToStr(Idx);
Img.Width := 100;
Img.Height := 100;
Img.Left := Left;
Img.Proportional := True;
Left := Left + 101;
Img.Top := Top;
Img.Picture.Assign(Graph);
Img.BringToFront;
Count := Count + 1;
Img.OnClick := getImgInfo(Img, edPath.Text, edFileName.Text); //Error line
if Count = 2 then
begin
Left := 5;
Top := Top + 101;
Count := 0;
end;
end;
except on E : Exception do
ShowMessage('Error: :' + E.Message);
end;
end;
end;
Error:
[dcc32 Error] uMain.pas(74): E2010 Incompatible types: 'TNotifyEvent' and 'procedure, untyped pointer or untyped parameter'
What is wrong?
Thanks!
The OnClick event handler of TImage is a TNotifyEvent, so you can only assign such a procedure to it. This is a method (a procedure belonging to an object), and it takes a single parameter, Sender of type TObject. So this will work:
procedure TfMain.ImageClickHandler(Sender: TObject);
begin
// Do something
end;
...
Img.OnClick := ImageClickHandler;
You need some kind of data structure to store your data. Perhaps
type
TImageData = record
Image: TImage;
ImageTitle: string;
ImageFileName: string;
Photographer: string;
DateTaken: TDateTime;
end;
and
var
ImageData = array of TImageData;
Or, more similar to your code:
type
TImageData = record
Image: TImage;
AssociatedEditControl1,
AssociatedEditControl2: TEdit;
end;
var
ImageData = array of TImageData;
Then you set the length of ImageData to openImg.Files.Count, and use Image and AssociatedEditControl1 and AssociatedEditControl2 instead of the local variables. After all, you want to be able to access these controls easily. You could also set the Tag of the TImage to the current value of i, and then in ImageClickHandler, you can check Self.Tag to access ImageData[Self.Tag].AssociatedEditControl1, say.
(But I still think you should separate the internal data from the GUI better. You also need to fix your memory leak.)
Try something more like this instead:
type
PImageInfo = ^ImageInfo;
ImageInfo = record
Path: String;
FileName: String;
Img: TImage;
EdPath: TEdit;
EdFileName : TEdit;
// anything else you need...
end;
private
{ Private declarations }
Images: array of ImageInfo;
procedure ImageClicked(Sender: TObject);
procedure TfMain.Button1Click(Sender: TObject);
var
i, ImgLeft, ImgTop, Count : integer;
Graph : TGraphic;
Info: PImageInfo;
begin
if not openImg.Execute then Exit;
ImgLeft := 5;
ImgTop := 5;
Count := Length(Images);
try
SetLength(Images, Count + openImg.Files.Count);
for I := 0 to openImg.Files.Count-1 do
begin
Graph := TPngImage.Create;
try
Graph.LoadFromFile(openImg.Files[i]);
Info := #Images[Count];
Info.Path := ExtractFilePath(openImg.Files[i]);
Info.FileName := ExtractFileName(openImg.Files[i]);
Info.Img := nil;
Info.EdPath := nil;
Info.EdFileName := nil;
try
Info.EdPath := TEdit.Create(pImgs);
Info.EdPath.Left := ImgLeft + 101;
Info.EdPath.Visible := False;
Info.EdPath.Text := Path;
Info.EdFileName := TEdit.Create(pImgs);
Info.EdFileName.Left := ImgLeft + 101;
Info.EdFileName.Visible := False;
Info.EdFileName.Text := Images[Count].FileName;
Info.Img := TImage.Create(pImgs);
Info.Img.Parent := pImgs;
Info.Img.Tag := Count;
Info.Img.Name := 'Img_'+IntToStr(Count);
Info.Img.SetBounds(ImgLeft, ImgTop, 100, 100);
Info.Img.Proportional := True;
Info.Img.OnClick := ImageClicked;
Info.Img.Picture.Assign(Graph);
Info.Img.BringToFront;
except
Info.EdPath.Free;
Info.EdFileName.Free;
Info.Img.Free;
raise;
end;
finally
Graph.Free;
end;
Inc(Count);
if (Count mod 2) = 0 then
begin
ImgLeft := 5;
Inc(ImgTop, 101);
end else
Inc(ImgLeft, 101);
end;
except
on E : Exception do
begin
SetLength(Images, Count);
ShowMessage('Error: :' + E.Message);
end;
end;
end;
procedure TfMain.ImageClicked(Sender: TObject);
var
Info: PImageInfo;
begin
Info := #Images[(Sender as TImage).Tag];
// use Info as needed...
end;
I am trying to display a table using ShowMessage that looks like this:
short | Description for "short"
verylongtext | Description for "verylongtext"
How do I get two correctly aligned columns like that in a simple message dialog?
I tried to align the columns using spaces, but the font of ShowMessage is variable. Then I tried to align them using tab characters, but I do not know how to calculate the proper tab count for each row.
Is there a reliable way to calculate the tab count?
PS: I would like to avoid writing a custom dialog for this purpose.
You could use a list view in a custom dialog box, as well.
My class supports the standard Windows icons (and sounds): information, warning, error, confirmation, none. Here is the icon-less version:
It is easy to use:
TTableDialog.ShowTable
(
Self,
'Audio Properties',
['Duration', 'Samples per channel', 'Total data size', 'Channels', 'Bits per sample', 'Sample rate', 'Bitrate'],
['1 h 15 min 0 s', '216 000 000', '824 MB', '1', '32', '48 kHz', '1 536 kbit/sec'],
mtInformation
)
It supports DPI scaling (high DPI) and all Windows versions from Windows XP (it might work on Windows 2000 as well, I just haven't tested that) to Windows 10:
The table is a list view, so you get all its benefits, like a scrollbar, truncation ellipses, and tooltips:
You can also specify the dialog's size to make it fit the contents:
TTableDialog.ShowTable
(
Self,
'Audio Properties',
['Duration', 'Samples per channel', 'Total data size', 'Channels', 'Bits per sample', 'Sample rate', 'Bitrate', 'Maximum fractional sample value'],
['1 h 15 min 0 s', '216 000 000', '824 MB', '1', '32', '48 kHz', '1 536 kbit/sec', '0.1'],
mtInformation,
360,
240
)
Of course, the OK button is both Default and Cancel, so you can dismiss the dialog with Enter or Escape.
Finally, pressing Ctrl+C will copy the table to clipboard.
Full source code:
uses
ComCtrls, Math, Clipbrd;
type
TTableDialog = class
strict private
type TFormData = class(TComponent)
public
ListView: TListView;
IconKind: PWideChar;
Icon: HICON;
LIWSD: Boolean;
end;
class function Scale(X: Integer): Integer;
class procedure FormShow(Sender: TObject);
class procedure FormDestroy(Sender: TObject);
class procedure FormPaint(Sender: TObject);
class procedure FormKeyPress(Sender: TObject; var Key: Char);
class procedure LVToClipboard(AListView: TListView);
public
class procedure ShowTable(AOwner: TCustomForm; const ACaption: string;
const ANames, AValues: array of string;
ADialogType: TMsgDlgType = mtInformation;
const AWidth: Integer = 360; const AHeight: Integer = 200);
end;
class procedure TTableDialog.FormShow(Sender: TObject);
var
FormData: TFormData;
ComCtl: HMODULE;
LoadIconWithScaleDown: function(hinst: HINST; pszName: LPCWSTR; cx: Integer;
cy: Integer; var phico: HICON): HResult; stdcall;
begin
if not (Sender is TForm) then
Exit;
if not (TObject(TForm(Sender).Tag) is TFormData) then
Exit;
TForm(Sender).OnShow := nil;
FormData := TFormData(TForm(Sender).Tag);
if FormData.IconKind = nil then
Exit;
ComCtl := LoadLibrary('ComCtl32.dll');
if ComCtl <> 0 then
begin
try
LoadIconWithScaleDown := GetProcAddress(ComCtl, 'LoadIconWithScaleDown');
if Assigned(LoadIconWithScaleDown) then
FormData.LIWSD := Succeeded(LoadIconWithScaleDown(0, FormData.IconKind,
Scale(32), Scale(32), FormData.Icon));
finally
FreeLibrary(ComCtl);
end;
end;
if not FormData.LIWSD then
FormData.Icon := LoadIcon(0, FormData.IconKind);
end;
class procedure TTableDialog.FormDestroy(Sender: TObject);
var
FormData: TFormData;
begin
if not (Sender is TForm) then
Exit;
if not (TObject(TForm(Sender).Tag) is TFormData) then
Exit;
FormData := TFormData(TForm(Sender).Tag);
if (FormData.Icon <> 0) and FormData.LIWSD then
DestroyIcon(FormData.Icon);
end;
class procedure TTableDialog.FormKeyPress(Sender: TObject; var Key: Char);
var
FormData: TFormData;
begin
if not (Sender is TForm) then
Exit;
if not (TObject(TForm(Sender).Tag) is TFormData) then
Exit;
FormData := TFormData(TForm(Sender).Tag);
case Key of
^C:
LVToClipboard(FormData.ListView);
end;
end;
class procedure TTableDialog.FormPaint(Sender: TObject);
var
FormData: TFormData;
Frm: TForm;
Y: Integer;
begin
if not (Sender is TForm) then
Exit;
if not (TObject(TForm(Sender).Tag) is TFormData) then
Exit;
Frm := TForm(Sender);
FormData := TFormData(TForm(Sender).Tag);
Y := Frm.ClientHeight - Scale(25 + 8 + 8);
Frm.Canvas.Brush.Color := clWhite;
Frm.Canvas.FillRect(Rect(0, 0, Frm.ClientWidth, Y));
Frm.Canvas.Pen.Color := $00DFDFDF;
Frm.Canvas.MoveTo(0, Y);
Frm.Canvas.LineTo(Frm.ClientWidth, Y);
if FormData.Icon <> 0 then
DrawIconEx(Frm.Canvas.Handle, Scale(8), Scale(8), FormData.Icon,
Scale(32), Scale(32), 0, 0, DI_NORMAL);
end;
class procedure TTableDialog.LVToClipboard(AListView: TListView);
function GetRow(AIndex: Integer): string;
begin
if InRange(AIndex, 0, AListView.Items.Count - 1) and (AListView.Items[AIndex].SubItems.Count = 1) then
Result := AListView.Items[AIndex].Caption + #9 + AListView.Items[AIndex].SubItems[0]
else
Result := '';
end;
var
S: string;
i: Integer;
begin
if AListView = nil then
Exit;
S := GetRow(0);
for i := 1 to AListView.Items.Count - 1 do
S := S + sLineBreak + GetRow(i);
Clipboard.AsText := S;
end;
class function TTableDialog.Scale(X: Integer): Integer;
begin
Result := MulDiv(X, Screen.PixelsPerInch, 96);
end;
class procedure TTableDialog.ShowTable(AOwner: TCustomForm; const ACaption: string;
const ANames, AValues: array of string;
ADialogType: TMsgDlgType = mtInformation;
const AWidth: Integer = 360; const AHeight: Integer = 200);
const
Sounds: array[TMsgDlgType] of Integer =
(MB_ICONWARNING, MB_ICONERROR, MB_ICONINFORMATION, MB_ICONQUESTION, 0);
Icons: array[TMsgDlgType] of MakeIntResource =
(IDI_WARNING, IDI_ERROR, IDI_INFORMATION, IDI_QUESTION, nil);
var
dlg: TForm;
lv: TListView;
btn: TButton;
i: Integer;
snd: Integer;
begin
if Length(ANames) <> Length(AValues) then
raise Exception.Create('The lengths of the columns don''t match.');
dlg := TForm.Create(AOwner);
try
dlg.BorderStyle := bsDialog;
dlg.Caption := ACaption;
dlg.Width := Scale(AWidth);
dlg.Height := Scale(AHeight);
dlg.Position := poOwnerFormCenter;
dlg.Scaled := False;
dlg.Font.Name := 'Segoe UI';
dlg.Font.Size := 9;
dlg.Tag := NativeInt(TFormData.Create(dlg));
TFormData(dlg.Tag).IconKind := Icons[ADialogType];
dlg.OnShow := FormShow;
dlg.OnDestroy := FormDestroy;
dlg.OnPaint := FormPaint;
dlg.OnKeyPress := FormKeyPress;
dlg.KeyPreview := True;
btn := TButton.Create(dlg);
btn.Parent := dlg;
btn.Caption := 'OK';
btn.Default := True;
btn.Cancel := True;
btn.ModalResult := mrOk;
btn.Width:= Scale(75);
btn.Height := Scale(25);
btn.Left := dlg.ClientWidth - btn.Width - Scale(8);
btn.Top := dlg.ClientHeight - btn.Height - Scale(8);
lv := TListView.Create(dlg);
TFormData(dlg.Tag).ListView := lv;
lv.Parent := dlg;
lv.DoubleBuffered := True;
lv.ReadOnly := True;
lv.BorderStyle := bsNone;
lv.Left := Scale(8) + IfThen(Icons[ADialogType] <> nil, Scale(32 + 8));
lv.Top := Scale(8);
lv.Width := dlg.ClientWidth - Scale(16) - IfThen(Icons[ADialogType] <> nil, Scale(32 + 8));
lv.Height := dlg.ClientHeight - Scale(16 + 8 + 4) - btn.Height;
lv.ViewStyle := vsReport;
lv.RowSelect := True;
lv.ShowColumnHeaders := False;
with lv.Columns.Add do
begin
Caption := 'Name';
Width := Scale(150);
end;
with lv.Columns.Add do
begin
Caption := 'Value';
Width := lv.ClientWidth - lv.Columns[0].Width -
GetSystemMetricsForWindow(SM_CXVSCROLL, dlg.Handle) - scale(2);
end;
for i := 0 to High(ANames) do
with lv.Items.Add do
begin
Caption := ANames[i];
SubItems.Add(AValues[i]);
end;
snd := Sounds[ADialogType];
if snd <> 0 then
MessageBeep(snd);
dlg.ShowModal;
finally
dlg.Free;
end;
end;
If you're not writing a custom dialog for this, when will you? It's not that hard. Just create a form, drop a TMemo on it and make that memo readonly. You can set a monospaced font like Courier New, and your problem is solved. You got the advantage of scrollbars and selection too, and you can choose to make it non-modal.
I would even recommend showing this type of data in a grid (like TStringGrid) instead of a memo or label.
Calculating how to display this text in a messagebox will require much more effort than just creating a custom dialog.
Just created something that shows a popup like this:
Just call the procedure below, and add a TStringList as a parameter.
Of course you could pimp this by using a TListView, icons, scrollbars, etc.
Put it in a separate unit, and you'll always be able to easily show stuff like this.
uses ..., StdCtrls, ExtCtrls;
procedure ShowTablePopup(SL:TStringList);
var
LButtonOK: TButton;
LMemo: TMemo;
LPanel: TPanel;
LForm: TForm;
begin
LForm := TForm.Create(Application);
LMemo := TMemo.Create(LForm);
LPanel := TPanel.Create(LForm);
LButtonOK := TButton.Create(LForm);
LForm.Left := 0;
LForm.Top := 0;
LForm.Caption := 'Values';
LForm.ClientHeight := 250;
LForm.ClientWidth := 400;
LMemo.Parent := LForm;
LMemo.AlignWithMargins := True;
LMemo.Left := 3;
LMemo.Top := 3;
LMemo.Width := 295;
LMemo.Height := 226;
LMemo.Align := alClient;
LMemo.Font.Name := 'Courier New';
LMemo.Lines.Assign(SL);
LPanel.Parent := LForm;
LPanel.Caption := '';
LPanel.Left := 0;
LPanel.Top := 232;
LPanel.Width := 301;
LPanel.Height := 37;
LPanel.Align := alBottom;
LPanel.BevelOuter := bvNone;
LButtonOK.Parent := LPanel;
LButtonOK.AlignWithMargins := True;
LButtonOK.Left := 223;
LButtonOK.Top := 3;
LButtonOK.Width := 75;
LButtonOK.Height := 31;
LButtonOK.Align := alRight;
LButtonOK.Caption := '&OK';
LButtonOK.ModalResult := mrOk;
LButtonOK.Default := True;
LForm.ShowModal;
end;
Example on how to use it:
var
SL:TStringList;
begin
SL := TStringList.Create;
try
SL.Add('short | Description for "short"');
SL.Add('verylongtext | Description for "verylongtext"');
ShowTablePopup(SL);
finally
SL.Free;
end;
end;