TObjectList finding an Item - firemonkey

I'm constructing a TObjectList which will store objects of class tButton:
...
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure FormCreate(Sender: TObject);
public
function FindButton (const aButtonName: string; var aButton: tButton) : Boolean;
end;
...
var ButtonObjectList : TObjectList<TButton>;
function TForm1.FindButton (const aButtonName: string; var aButton: tButton) : Boolean;
...
var b : Integer;
begin
Result := False;
for b := Low (ButtonObjectList.Count) to High (ButtonObjectList.Count) do
if ButtonObjectList.Items [b].Name = aButtonName then begin
Result := True;
aButton := ButtonObjectList.Items [b];
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ButtonObjectList := TObjectList<TButton>.Create(True);
ButtonObjectList.Add(Button1);
ButtonObjectList.Add(Button2);
ButtonObjectList.Add(Button3);
end;
further, in unit untRetrieveButton:
...
var Button : TButton;
procedure FindAButton;
begin
if Form1.FindButton ('Button 1', Button) then
ShowMessage ('Button found')
else
ShowMessage ('Button not found')
end;
I want to get back an arbitrary button stored in the ButtonObjectList, but at this point, I only know the button's name. From what I've learn at the TObjectList documentation, the only way to achieve this, is traversing the entire Items list, and compare the parameter aButtonName with the Button's name in the TObjectList, as in
function TForm1.FindButton (const aButtonName: string; var aButton: tButton) : Boolean;
Is this right, or there is a better and most efficient way to retrieve an arbitrary Button by it's name?

I think, if you have only limited amount of buttons it doesn't matter and the speed should be ok.
If I have such cases I often use a solution like this:
var
ButtonDict: TDictionary<String,TButton>;
FoundButton: TButton;
begin
...
ButtonDict.Add(UpperCase(Button1.Name),Button1);
ButtonDict.Add(UpperCase(Button2.Name),Button2);
ButtonDict.Add(UpperCase(Button3.Name),Button3);
...
//fast access...
if ButtonDict.TryGetValue(UpperCase(NameOfButton),FoundButton) then
begin
//... now you got the button...
end else
begin
// Button not found...
end;
...
end;

Related

Delphi 11.2 How to set breakpoint when tbitbtn change enabled=true to enanbled=false [duplicate]

As an example, given the code extract below, I would like to define a breakpoint that triggers whenever the object field value changes and optionally, breaks on a condition (False or True in this case).
type
TForm1 = class(TForm)
EnableButton: TButton;
DisableButton: TButton;
procedure EnableButtonClick(Sender: TObject);
procedure DisableButtonClick(Sender: TObject);
private
FValue: Boolean; // <== Would like to define a breakpoint here whenever FValue changes.
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.DisableButtonClick(Sender: TObject);
begin
FValue := False;
end;
procedure TForm1.EnableButtonClick(Sender: TObject);
begin
FValue := True;
end;
Run the application under the debugger,
select 'Run' from the IDE menu then select 'Add Breakpoint' at the very bottom, then 'Data Breakpoint...'.
enter 'Form1.FValue' as input to the 'Adress:' field. You can also set your condition in the same dialog.
Some additional information thanks to Sertac answer and comment from David.
One can define a breakpoint based on changes in an array item with a condition.
In this case the data breakpoint is defined as follow:
Form1.FBooleans[0] = True
Code extract:
type
TBooleanArray = array of Boolean;
TForm1 = class(TForm)
EnableButton: TButton;
DisableButton: TButton;
procedure EnableButtonClick(Sender: TObject);
procedure DisableButtonClick(Sender: TObject);
private
FBooleans: TBooleanArray; // Breakpoint defined here with the condition
public
constructor Create(AOwner: TComponent); override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TForm1.Create(AOwner: TComponent);
var
AIndex: Integer;
begin
inherited;
SetLength(FBooleans, 3);
for AIndex := 0 to Length(FBooleans) - 1 do
begin
FBooleans[AIndex] := (AIndex mod 2) = 1;
end;
end;
procedure TForm1.DisableButtonClick(Sender: TObject);
begin
FBooleans[0] := False;
end;
procedure TForm1.EnableButtonClick(Sender: TObject);
begin
FBooleans[0] := True; // Beakpoint stops here on condition.
end;

Delphi Exponent Calculator

Sorry for my English, as I am from Germany.
I built a program: http://i.epvpimg.com/I0xie.png
And I want an exponent calculator (I am learning for test in school), but I have a problem...
If I do the number "Zahl" (meaning "number" in German). For example: Number= "2", then I do exponent = "1".
Normally I should get the result 2 but I am getting a 4, why?
What is the problem?
Here is my Code:
unit unit_oberflaeche;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
ExtCtrls, unit_inhalt;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Edit2: TEdit;
Label1: TLabel;
Label2: TLabel;
Panel1: TPanel;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
rechner: Texponentrechner;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
rechner := Texponentrechner.Create;
end;
procedure TForm1.Button1Click(Sender: TObject);
VAR i, LVexponent, LVzahl, result: INTEGER;
BEGIN
LVexponent := StrToInt(Edit2.Text);
LVzahl := StrToInt(Edit1.Text);
rechner.set_exponent(LVexponent);
rechner.set_zahl(LVzahl);
FOR i := 1 TO LVexponent DO
BEGIN
result := result * LVzahl
end;
//result := LVzahl;
Panel1.Caption := IntToStr(result);
end;
end.
And here is the other part:
unit unit_inhalt;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils;
TYPE
Texponentrechner = class
private
{ private declarations }
Fexponent : INTEGER;
Fzahl : INTEGER;
public
{ public declarations }
procedure set_exponent (WPexponent:INTEGER);
procedure set_zahl (WPzahl:INTEGER);
function berechne_betrag():INTEGER;
end;
implementation
procedure Texponentrechner.set_exponent(WPexponent:INTEGER);
BEGIN
Fexponent := WPexponent;
end;
procedure Texponentrechner.set_zahl(WPzahl:INTEGER);
BEGIN
Fzahl := WPzahl;
end;
function Texponentrechner.berechne_betrag():INTEGER;
BEGIN
result := Fzahl * Fzahl;
end;
end.
I assume that your exponent calculation has to be done in Texponentrechner class. First, your calculation there is wrong because it returns your number multiplied by itself, and second you are never calling that function in the first place.
So your berechne_betrag function should look like this:
function Texponentrechner.berechne_betrag(): integer;
var i: integer;
begin
Result := 1;
for i := 1 to Fexponent do
Result := Result * Fzahl;
end;
Then you should actually call that function to get the result
rechner.set_exponent(LVexponent);
rechner.set_zahl(LVzahl);
result := rechner.berechne_betrag;
Panel1.Caption := IntToStr(result);
Also you are creating rechner object instance in FormCreate, but you are never releasing it and thus you are creating memory leak. You should call rechner.Free when you are finished using object. Since you have made it global var you create in FormCreate, proper place to release it will be in FormDestroy
But even better practice would be to make it local to Button1Click method.
...
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Edit2: TEdit;
Label1: TLabel;
Label2: TLabel;
Panel1: TPanel;
procedure Button1Click(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
var
LVexponent, LVzahl, result: integer;
rechner: Texponentrechner;
begin
rechner := Texponentrechner.Create;
try
LVexponent := StrToInt(Edit2.Text);
LVzahl := StrToInt(Edit1.Text);
rechner.set_exponent(LVexponent);
rechner.set_zahl(LVzahl);
result := rechner.berechne_betrag;
Panel1.Caption := IntToStr(result);
finally
rechner.Free;
end;
end;
end.

Delphi: ListView (vsReport) single column header caption with custom font color?

In a ListView with vsReport ViewStyle, how can I customize the font color of just any single column header caption? For example (the second column header caption has a red font color):
I would handle the NM_CUSTOMDRAW header notification code and respond to this notification message with the CDRF_NEWFONT return code at the CDDS_ITEMPREPAINT rendering stage. The following code shows how to extend list view controls to have the event for specifying header item font color:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, CommCtrl, StdCtrls;
type
TGetHeaderItemFontColorEvent = procedure(Sender: TCustomListView;
ItemIndex: Integer; var FontColor: TColor) of object;
TListView = class(ComCtrls.TListView)
private
FHeaderHandle: HWND;
FOnGetHeaderItemFontColor: TGetHeaderItemFontColorEvent;
procedure WMNotify(var AMessage: TWMNotify); message WM_NOTIFY;
protected
procedure CreateWnd; override;
published
property OnGetHeaderItemFontColor: TGetHeaderItemFontColorEvent read
FOnGetHeaderItemFontColor write FOnGetHeaderItemFontColor;
end;
type
TForm1 = class(TForm)
ListView1: TListView;
procedure FormCreate(Sender: TObject);
private
procedure GetHeaderItemFontColor(Sender: TCustomListView;
ItemIndex: Integer; var FontColor: TColor);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TListView }
procedure TListView.CreateWnd;
begin
inherited;
FHeaderHandle := ListView_GetHeader(Handle);
end;
procedure TListView.WMNotify(var AMessage: TWMNotify);
var
FontColor: TColor;
NMCustomDraw: TNMCustomDraw;
begin
if (AMessage.NMHdr.hwndFrom = FHeaderHandle) and
(AMessage.NMHdr.code = NM_CUSTOMDRAW) then
begin
NMCustomDraw := PNMCustomDraw(TMessage(AMessage).LParam)^;
case NMCustomDraw.dwDrawStage of
CDDS_PREPAINT:
AMessage.Result := CDRF_NOTIFYITEMDRAW;
CDDS_ITEMPREPAINT:
begin
FontColor := Font.Color;
if Assigned(FOnGetHeaderItemFontColor) then
FOnGetHeaderItemFontColor(Self, NMCustomDraw.dwItemSpec, FontColor);
SetTextColor(NMCustomDraw.hdc, ColorToRGB(FontColor));
AMessage.Result := CDRF_NEWFONT;
end;
else
AMessage.Result := CDRF_DODEFAULT;
end;
end
else
inherited;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
ListView1.OnGetHeaderItemFontColor := GetHeaderItemFontColor;
end;
procedure TForm1.GetHeaderItemFontColor(Sender: TCustomListView;
ItemIndex: Integer; var FontColor: TColor);
begin
case ItemIndex of
0: FontColor := clRed;
1: FontColor := clGreen;
2: FontColor := clBlue;
end;
end;
end.
The whole project you can download from here. Here's the result of the above example:
You can get the native header control from the listview and then mark the specific item of your column as owner drawn. You only need to change the text color (if you don't remove the string flag) when the header item requests to be drawn. The drawing message will be sent to the header's parent - the listview, hence you need to handle the message there. See here for owner drawn header controls.
Example code:
type
TForm1 = class(TForm)
ListView1: TListView;
procedure FormCreate(Sender: TObject);
...
private
FLVHeader: HWND;
FSaveLVWndProc: TWndMethod;
procedure LVWndProc(var Msg: TMessage);
procedure SetHeaderItemStyle(Index: Integer);
end;
..
uses commctrl;
..
procedure TForm1.FormCreate(Sender: TObject);
begin
FLVHeader := ListView_GetHeader(ListView1.Handle);
SetHeaderItemStyle(1);
FSaveLVWndProc := ListView1.WindowProc;
ListView1.WindowProc := LVWndProc;
end;
procedure TForm1.SetHeaderItemStyle(Index: Integer);
var
HeaderItem: THDItem;
begin
HeaderItem.Mask := HDI_FORMAT or HDI_TEXT or HDI_LPARAM;
Header_GetItem(FLVHeader, 1, HeaderItem);
HeaderItem.Mask := HDI_FORMAT;
HeaderItem.fmt := HeaderItem.fmt or HDF_OWNERDRAW;
Header_SetItem(FLVHeader, 1, HeaderItem);
end;
procedure TForm1.LVWndProc(var Msg: TMessage);
begin
FSaveLVWndProc(Msg); // thanks to #Kobik (cause SO if called later then WM_NOTIFY case on some (all other then mine?) machines)
case Msg.Msg of
WM_DRAWITEM:
if (TWmDrawItem(Msg).DrawItemStruct.CtlType = ODT_HEADER) and
(TWmDrawItem(Msg).DrawItemStruct.hwndItem = FLVHeader) and
(TWmDrawItem(Msg).DrawItemStruct.itemID = 1) then
SetTextColor(TWmDrawItem(Msg).DrawItemStruct.hDC, ColorToRGB(clRed));
WM_NOTIFY:
if (TWMNotify(Msg).NMHdr.hwndFrom = FLVHeader) and
(TWMNotify(Msg).NMHdr.code = HDN_ITEMCHANGED) then
// also try 'HDN_ENDTRACK' if it doesn't work as expected
SetHeaderItemStyle(1);
WM_DESTROY: ListView1.WindowProc := FSaveLVWndProc;
end;
end;

how can i free a Tpanel That have a TbitBtn that calls to free the Tpanel

I create a Custom Tpanel and inside I put various Custom Components ...
procedure Panel_Comp(Location: TWinControl; NumOfComp: Integer;Left,Top,Height,width:Integer);
begin
MyPanel := TsPanel.Create(Conf);
MyPanel.Name := 'MyPanel' + IntToStr(NumOfComp);
MyPanel.Parent := Location;
MyPanel.Left := Left;
MyPanel.Top := Top;
MyPanel.Height := Height;
MyPanel.Width := width;
MyPanel.Caption := '';
end;
and i call it like this
Panel_Comp(Conf.ScrollBox1,1,8,10,70,322);
in the same logic i put inside the new panel other custom components including a tBitbtn the have a onclick event..
procedure BitBtn_Comp(Location: TWinControl; NumOfComp: Integer; Left,Top,Height,Width,ImageNum: Integer);
begin
MyBitBtn := TBitBtn.Create(Conf);
......
MyBitBtn.tag := NumOfComp;
MyBitBtn.OnClick:= Conf.CloseCurrentPanel;
end;
In the main Forn The TConf.CloseCurrentPanel;
procedure TConf.CloseCurrentPanel(Sender: TObject);
var
panelComp: TComponent;
begin
panelComp := FindComponentEx('Conf.MyPanel'+ IntToStr(TBitBtn(Sender).tag);
TPanel(panelComp).Free;
Application.ProcessMessages;
end;
When I call that I get access violation...
I think is something that I must free all the components inside the panel before free the panel but how I free the BitBtn before the panel and continue the action of the click event?
Here is the FindComponetEx function instead you need it...
function FindComponentEx(const Name: string): TComponent;
var
FormName: string;
CompName: string;
P: Integer;
Found: Boolean;
Form: TForm;
I: Integer;
begin
// Split up in a valid form and a valid component name
P := Pos('.', Name);
if P = 0 then
begin
raise Exception.Create('No valid form name given');
end;
FormName := Copy(Name, 1, P - 1);
CompName := Copy(Name, P + 1, High(Integer));
Found := False;
// find the form
for I := 0 to Screen.FormCount - 1 do
begin
Form := Screen.Forms[I];
// case insensitive comparing
if AnsiSameText(Form.Name, FormName) then
begin
Found := True;
Break;
end;
end;
if Found then
begin
for I := 0 to Form.ComponentCount - 1 do
begin
Result := Form.Components[I];
if AnsiSameText(Result.Name, CompName) then Exit;
end;
end;
Result := nil;
end;
The AV occurs because you are destroying a component (MyBitBtn) while it is still handling Windows messages. The solution is to postpone the destruction until later via PostMessage, similar to this:
unit Unit1;
interface
uses
Windows,
Messages,
SysUtils,
Variants,
Classes,
Graphics,
Controls,
Forms,
Dialogs,
ExtCtrls,
StdCtrls;
const
UM_DESTROYPANEL = WM_APP + 623; // some "unique" number; UM = user message
type
TConf = class(TForm)
Panel1: TPanel;
Button1: TButton;
procedure Button1Click(Sender: TObject);
strict private
procedure UMDestroyPanel(var Message: TMessage); message UM_DESTROYPANEL;
public
{ Public-Deklarationen }
end;
var
Conf: TConf;
implementation
{$R *.dfm}
procedure TConf.Button1Click(Sender: TObject);
begin
PostMessage(Handle, UM_DESTROYPANEL, 0, 0);
end;
procedure TConf.UMDestroyPanel(var Message: TMessage);
begin
Panel1.Free;
end;
end.
If needed you can use wParam and lParam to pass through parameters like so:
procedure TConf.Button1Click(Sender: TObject);
begin
PostMessage(Handle, UM_DESTROYPANEL, WPARAM(Panel1), 0);
end;
procedure TConf.UMDestroyPanel(var Message: TMessage);
begin
TObject(Message.WParam).Free;
end;
EDIT:
In your situation I'd probably rewrite TConf.CloseCurrentPanel like this:
procedure TConf.CloseCurrentPanel(Sender: TObject);
var
panelComp: TComponent;
begin
panelComp := FindComponentEx('Conf.MyPanel'+ IntToStr(TBitBtn(Sender).Tag);
PostMessage(Handle, UM_DESTROYPANEL, WPARAM(panelComp), 0);
end;
Alternatively you can pass through the Tag (might be the better solution because there's less casting involved):
procedure TConf.CloseCurrentPanel(Sender: TObject);
begin
PostMessage(Handle, UM_DESTROYPANEL, TBitBtn(Sender).Tag, 0);
end;
procedure TConf.UMDestroyPanel(var Message: TMessage);
var
panelComp: TComponent;
begin
panelComp := FindComponentEx('Conf.MyPanel'+ IntToStr(Message.WParam));
panelComp.Free;
end;
AFAICT the Application.ProcessMessages isn't needed.
procedure TConf.CloseCurrentPanel(Sender: TObject);
var
panelComp: TComponent;
begin
panelComp := FindComponentEx('Conf.MyPanel'+ IntToStr(TBitBtn(Sender).tag);
//Where you need to determine 'PanelComp' if there are.
if Assigned(panelComp) and (PanelComp is TPanel) then
TPanel(panelComp).Free;
Application.ProcessMessages;
end;

How can you change the text orientation in cells in the fixed rows in a Delphi TStringGrid

I have a standard TStringGrid on a form.
I have one Fixed Row in the grid that contains a number of columns, which are all TGridColumns objects. I have set the column titles using the object inspector and the default orientation is horizontal. Is there any way you can make the orientation vertical (like you can in cells in Excel)?
Here's how to render the first row's text vertically in Lazarus:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, Grids,
StdCtrls;
type
TStringGrid = class(Grids.TStringGrid)
protected
procedure DrawCellText(ACol, ARow: Integer; ARect: TRect;
AState: TGridDrawState; AText: String); override;
end;
type
TForm1 = class(TForm)
Button1: TButton;
StringGrid1: TStringGrid;
procedure Button1Click(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
procedure TStringGrid.DrawCellText(ACol, ARow: Integer; ARect: TRect;
AState: TGridDrawState; AText: String);
var
TextPosition: TPoint;
begin
if ARow = 0 then
begin
Canvas.Font.Orientation := 900;
TextPosition.X := ARect.Left +
((ARect.Right - ARect.Left - Canvas.TextHeight(AText)) div 2);
TextPosition.Y := ARect.Bottom -
((ARect.Bottom - ARect.Top - Canvas.TextWidth(AText)) div 2);
Canvas.TextOut(TextPosition.X, TextPosition.Y, AText);
end
else
inherited DrawCellText(ACol, ARow, ARect, AState, AText);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
GridColumn: TGridColumn;
begin
for I := 0 to 4 do
begin
GridColumn := StringGrid1.Columns.Add;
GridColumn.Width := 24;
GridColumn.Title.Font.Orientation := 900;
GridColumn.Title.Layout := tlBottom;
GridColumn.Title.Caption := 'Column no. ' + IntToStr(I);
end;
StringGrid1.RowHeights[0] := 80;
end;
end.
Here's how to render the first row's text of the TStringGrid vertically in Delphi:
I would prefer to use the overriden DrawCell procedure because it seems to me as the easiest way to go because if you want to render the text simply in the OnDrawCell event then you should consider:
if you'll have the DefaultDrawing set to True then the text will already be rendered when the OnDrawCell event is fired, so here I would recommend e.g. to store the cell captions in a separate variable, not into Cells property so then no text will be rendered and you can draw your own stored captions vertically
if you'll have the DefaultDrawing set to False then you'll have to draw the whole cell by your own, including the 3D border, what is IMHO not so cool, and I would personally prefer to let the control draw the background for us
Here is the Delphi code which uses the overriden DrawCell procedure. The text is being centered inside of the cell rectangle; please note that I haven't used the DrawTextEx for text size measurement because this function doesn't take the changed font orientation into account.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Grids;
type
TStringGrid = class(Grids.TStringGrid)
protected
procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState); override;
end;
type
TForm1 = class(TForm)
Button1: TButton;
StringGrid1: TStringGrid;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TStringGrid.DrawCell(ACol, ARow: Longint; ARect: TRect;
AState: TGridDrawState);
var
LogFont: TLogFont;
TextPosition: TPoint;
NewFontHandle: HFONT;
OldFontHandle: HFONT;
begin
if ARow = 0 then
begin
GetObject(Canvas.Font.Handle, SizeOf(LogFont), #LogFont);
LogFont.lfEscapement := 900;
LogFont.lfOrientation := LogFont.lfEscapement;
NewFontHandle := CreateFontIndirect(LogFont);
OldFontHandle := SelectObject(Canvas.Handle, NewFontHandle);
TextPosition.X := ARect.Left +
((ARect.Right - ARect.Left - Canvas.TextHeight(Cells[ACol, ARow])) div 2);
TextPosition.Y := ARect.Bottom -
((ARect.Bottom - ARect.Top - Canvas.TextWidth(Cells[ACol, ARow])) div 2);
Canvas.TextRect(ARect, TextPosition.X, TextPosition.Y, Cells[ACol, ARow]);
NewFontHandle := SelectObject(Canvas.Handle, OldFontHandle);
DeleteObject(NewFontHandle);
end
else
inherited DrawCell(ACol, ARow, ARect, AState);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
for I := 0 to StringGrid1.ColCount - 1 do
begin
StringGrid1.ColWidths[I] := 24;
StringGrid1.Cells[I, 0] := 'Column no. ' + IntToStr(I);
end;
StringGrid1.RowHeights[0] := 80;
end;
end.
And here's how it looks like:

Resources