Communicating between frames in Delphi - delphi

I just started using Frames in Delphi.
That Frames are in FrameBar1 and they both are visible. Just for testing, first one contains one Button and second Frame contains one Edit.
I want to change text in Edit with click on Button (which are controls on two different frames).
How to communicate between frames?

The same way you would if the controls were in the same Form. Just prefix the Edit control with the Frame object that owns it, eg:
uses
Frame1Unit, Frame2Unit;
procedure TForm1.FormCreate(Sender: TObject);
begin
Frame1 := TFrame1.Create(Self);
Frame1.Parent := ...;
...
Frame2 := TFrame2.Create(Self);
Frame2.Parent := ...;
...
end;
uses
Frame2Unit;
procedure TFrame1.Button1Click(Sender: TObject);
begin
Frame2.Edit1.Text := '...';
end;
A better design would be to encapsulate the logic so Frame1 and Frame2 do not know about each other. Have Frame1 expose an event that it fires when the button is clicked, and then the parent Form can assign a handler to that event and assign the text on the Frame2, eg:
uses
Frame1Unit, Frame2Unit;
procedure TForm1.FormCreate(Sender: TObject);
begin
Frame1 := TFrame1.Create(Self);
Frame1.Parent := ...;
Frame1.OnNewText := Frame1Text;
...
Frame2 := TFrame2.Create(Self);
Frame2.Parent := ...;
...
end;
procedure TForm1.Frame1Text(Sender: TObject; const NewText: string);
begin
Frame2.EditText := NewText;
end;
type
TFrame1TextEvent = procedure(Sender: TObject; const NewText; string) of object;
TFrame1 = class(TFrame)
Button1: TButton;
procedure Button1Click(Sender: TObject);
public
OnNewText: TFrame1TextEvent;
end;
procedure TFrame1.Button1Click(Sender: TObject);
begin
if Assigned(OnNewText) then
OnNewText(Self, '...');
end;
type
TFrame2 = class(TFrame)
Edit1: TEdit;
private
function GetEditText: string;
procedure SetEditText(const Value: string);
public
property EditText: string read GetEditText write SetEditText;
end;
function TFrame2.GetEditText: string;
begin
Result := Edit1.Text;
end;
procedure TFrame2.SetEditText(const Value: string);
begin
Edit1.Text := Value;
end;

Related

delphi set the TExpander height

i've a TExpander component and i add some TTexts to it at runtime , the issue i'm facing is : how can this Expander's height be set based on the Number of the TTexts , something like AutoSize ?
the code i'm using :
procedure TForm1.Button5Click(Sender: TObject);
var
_DailyEvent:TDailyEvents;
Eventstext:TText;
_Y:Integer;
begin
_Y:=10;
For _DailyEvent in DailyEventsList do
begin
Eventstext:=TText.Create(Self);
Eventstext.Position.Y := _Y;
Eventstext.Align:=TAlignLayout.Top;
Eventstext.Height:=25;
Eventstext.TagString:=_DailyEvent.EventID;
Eventstext.Text:=_DailyEvent.EventName;
Eventstext.Parent:=Expander1;
inc(_Y, 15);
end;
Expander1.Height:=?
end;
here's what i get
thank you .
Actually, when you set Eventstext.Parent to expander1, this object added to protected field FContent. So, if you want calculate real size as sum of all inner controls, you must get this field.
You can "override" TExpander class like this:
type
// declare new TExpander class before form declaration
// thanks to this declaration we have access to protected fields
TExpander = class(FMX.StdCtrls.TExpander)
protected
procedure DoExpandedChanged; override;
public
function GetRealRect: TRectF;
end;
TForm2 = class(TForm)
expndr1: TExpander; // this is our new class, not "standart" TExpander
btnAdd10: TButton;
btnDelLast: TButton;
procedure btnAdd10Click(Sender: TObject);
procedure btnDelLastClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.fmx}
procedure TForm2.btnAdd10Click(Sender: TObject);
var
Eventstext: TText;
i: integer;
_Y: integer;
begin
_Y := 10;
For i := 1 to 10 do
begin
Eventstext := TText.Create(Self);
Eventstext.Position.Y := _Y;
Eventstext.Align := TAlignLayout.Top;
Eventstext.Height := 25;
Eventstext.Text := i.ToString;
Eventstext.Parent := expndr1;
inc(_Y, 25);
end;
// of course, this is not real Autosize,
// you can override AddObject in TExpander and change size in it,
// but you can`t get access to RemoveObject in FContent...
// thus, "AutoSize" will be limited only to adding items.
// I think the current way is much better than override AddObject...
expndr1.SetBoundsRect(expndr1.GetRealRect);
// or expndr1.height:=expndr1.GetRealRect.Height;
end;
procedure TForm2.btnDelLastClick(Sender: TObject);
begin
if expndr1.FContent.ChildrenCount <> 0 then
begin
expndr1.FContent.Children[expndr1.FContent.ChildrenCount - 1].Release;
expndr1.SetBoundsRect(expndr1.GetRealRect);
end;
end;
{ TExpander }
procedure TExpander.DoExpandedChanged;
begin
inherited;
SetBoundsRect(GetRealRect);
end;
function TExpander.GetRealRect: TRectF;
var
i: integer;
LControl: TControl;
begin
// above FContent are Button, Text and Checkbox
Result.TopLeft := AbsoluteRect.TopLeft;
Result.BottomRight := FContent.AbsoluteRect.TopLeft;
if FIsExpanded then
for i := 0 to FContent.ChildrenCount - 1 do
if FContent.Children[i] is TControl then
begin
LControl := TControl(FContent.Children[i]);
if LControl.Visible then
UnionRectF(Result, Result, LControl.ChildrenRect);
end;
if Result.Width = 0 then // if there are no controls in FContent.
Result.Width := Width;
end;

How do I pass an event as a function parameter?

I have a form that has a list of useful procedures that I have created, that I often use in every project. I am adding a procedure that makes it simple to add a click-able image over where would be the TAccessory of a TListBoxItem. The procedure intakes the ListBox currently, but I would also need it to intake which procedure to call for the OnClick Event for the image.. Here is my existing code:
function ListBoxAddClick(ListBox:TListBox{assuming I need to add another parameter here!! but what????}):TListBox;
var
i : Integer;
Box : TListBox;
BoxItem : TListBoxItem;
Click : TImage;
begin
i := 0;
Box := ListBox;
while i <> Box.Items.Count do begin
BoxItem := Box.ListItems[0];
BoxItem.Selectable := False;
Click := Timage.Create(nil);
Click.Parent := BoxItem;
Click.Height := BoxItem.Height;
Click.Width := 50;
Click.Align := TAlignLayout.alRight;
Click.TouchTargetExpansion.Left := -5;
Click.TouchTargetExpansion.Bottom := -5;
Click.TouchTargetExpansion.Right := -5;
Click.TouchTargetExpansion.Top := -5;
Click.OnClick := // this is where I need help
i := +1;
end;
Result := Box;
end;
The desired procedure would be defined in the form that is calling this function.
Since the OnClick event is of type TNotifyEvent you should define a parameter of that type. Look at this (I hope self-explaining) example:
type
TForm1 = class(TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
procedure TheClickEvent(Sender: TObject);
end;
implementation
procedure ListBoxAddClick(ListBox: TListBox; OnClickMethod: TNotifyEvent);
var
Image: TImage;
begin
Image := TImage.Create(nil);
// here is assigned the passed event method to the OnClick event
Image.OnClick := OnClickMethod;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
// here the TheClickEvent event method is passed
ListBoxAddClick(ListBox1, TheClickEvent);
end;
procedure TForm1.TheClickEvent(Sender: TObject);
begin
// do something here
end;

How can a control be notified when its parent receives and loses focus in Delphi?

As the title says, I'd like a component (say, a label) to be notified when it's parent (say, a panel) receives and loses focus. I wandered a bit in Delphi source, in hope of using TControl.Notify, but it's only used to notify child controls of some property changes like font and color. Any suggestions?
Whenever the active control in an application changes, a CM_FOCUSCHANGED message is broadcast to all controls. Simply intercept it, and act accordingly.
Also, I assumed that by when it's parent (say, a panel) receives and loses focus you mean whenever a (nested) child control on that parent/panel receives or loses focus.
type
TLabel = class(StdCtrls.TLabel)
private
function HasCommonParent(AControl: TWinControl): Boolean;
procedure CMFocusChanged(var Message: TCMFocusChanged);
message CM_FOCUSCHANGED;
end;
procedure TLabel.CMFocusChanged(var Message: TCMFocusChanged);
const
FontStyles: array[Boolean] of TFontStyles = ([], [fsBold]);
begin
inherited;
Font.Style := FontStyles[HasCommonParent(Message.Sender)];
end;
function TLabel.HasCommonParent(AControl: TWinControl): Boolean;
begin
Result := False;
while AControl <> nil do
begin
if AControl = Parent then
begin
Result := True;
Break;
end;
AControl := AControl.Parent;
end;
end;
If you don't like to subclass TJvGradientHeader, then it is possible to design this generically by the use of Screen.OnActiveControlChange:
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FHeaders: TList;
procedure ActiveControlChanged(Sender: TObject);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FHeaders := TList.Create;
FHeaders.Add(Label1);
FHeaders.Add(Label2);
Screen.OnActiveControlChange := ActiveControlChanged;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FHeaders.Free;
end;
function HasCommonParent(AControl: TWinControl; AMatch: TControl): Boolean;
begin
Result := False;
while AControl <> nil do
begin
if AControl = AMatch.Parent then
begin
Result := True;
Break;
end;
AControl := AControl.Parent;
end;
end;
procedure TForm1.ActiveControlChanged(Sender: TObject);
const
FontStyles: array[Boolean] of TFontStyles = ([], [fsBold]);
var
I: Integer;
begin
for I := 0 to FHeaders.Count - 1 do
TLabel(FHeaders[I]).Font.Style :=
FontStyles[HasCommonParent(Screen.ActiveControl, TLabel(FHeaders[I]))];
end;
Note that I chose TLabel to demonstrate this works also for TControl derivatives.

Delphi: show assigned Frame to Node of Tree View

I have no experience with frames.
How to use a Tree View with frames?
I need to switch among nods of the Tree View and show assigned Frame to the selected node.
Big big thank for help!!!
It doesn't really make any difference if the Data of nodes hold a pointer to a frame or any other kind of object, typecast the pointer to the type of object it holds.
Below code adds two frames ('Frame2' and 'Frame3', created by the IDE - much like a new form), as nodes of a TreeView, and sets the visibility of the selected node's frame to true and the deselected one's to false.
type
TForm1 = class(TForm)
TreeView1: TTreeView;
procedure FormCreate(Sender: TObject);
procedure TreeView1Change(Sender: TObject; Node: TTreeNode);
procedure TreeView1Changing(Sender: TObject; Node: TTreeNode;
var AllowChange: Boolean);
procedure FormDestroy(Sender: TObject);
end;
var
Form1: TForm1;
implementation
uses Unit2, Unit3;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
TreeView1.Align := alLeft;
with TFrame(TreeView1.Items.AddObject(nil, 'Frame2', TFrame2.Create(nil)).Data) do begin
Visible := False;
Parent := Self;
Align := alClient;
end;
with TFrame(TreeView1.Items.AddObject(nil, 'Frame3', TFrame3.Create(nil)).Data) do begin
Visible := False;
Parent := Self;
Align := alClient;
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
var
i: Integer;
begin
for i := 0 to TreeView1.Items.Count - 1 do
TFrame(TreeView1.Items[i].Data).Free;
end;
procedure TForm1.TreeView1Change(Sender: TObject; Node: TTreeNode);
begin
TFrame(Node.Data).Visible := True;
end;
procedure TForm1.TreeView1Changing(Sender: TObject; Node: TTreeNode;
var AllowChange: Boolean);
begin
if Assigned((Sender as TTreeView).Selected) then
TFrame(TTreeView(Sender).Selected.Data).Visible := False;
end;

Adding Characters one by one to TMemo

Could any one tell me how can I add characters one by one from a text file to a Memo?
The text file contains different paragraphs of texts. I want to add the characters of each paragraph one by one till the end of the paragraph. Then after 10 seconds delay the next paragraph to be shown in the Memo.
Thanks,
Sei
You would probably use a TTimer. Drop a TTimer, a TMemo and a TButton on your form. Then do
var
lines: TStringList;
pos: TPoint;
const
CHAR_INTERVAL = 75;
PARAGRAPH_INTERVAL = 1000;
procedure TForm6.Button1Click(Sender: TObject);
const
S_EMPTY_FILE = 'You are trying to display an empty file!';
begin
Memo1.ReadOnly := true;
Memo1.Clear;
Memo1.Lines.Add('');
pos := Point(0, 0);
if lines.Count = 0 then
raise Exception.Create(S_EMPTY_FILE);
while (pos.Y < lines.Count) and (length(lines[pos.Y]) = 0) do inc(pos.Y);
if pos.Y = lines.Count then
raise Exception.Create(S_EMPTY_FILE);
NextCharTimer.Enabled := true;
end;
procedure TForm6.FormCreate(Sender: TObject);
begin
lines := TStringList.Create;
lines.LoadFromFile('C:\Users\Andreas Rejbrand\Desktop\Test.txt');
end;
procedure TForm6.NextCharTimerTimer(Sender: TObject);
begin
NextCharTimer.Interval := CHAR_INTERVAL;
Memo1.Lines[Memo1.Lines.Count - 1] := Memo1.Lines[Memo1.Lines.Count - 1] + lines[pos.Y][pos.X + 1];
inc(pos.X);
if pos.X = length(lines[pos.Y]) then
begin
NextCharTimer.Interval := PARAGRAPH_INTERVAL;
pos.X := 0;
repeat
inc(pos.Y);
Memo1.Lines.Add('');
until (pos.Y = lines.Count) or (length(lines[pos.Y]) > 0);
end;
if pos.Y = lines.Count then
NextCharTimer.Enabled := false;
end;
A thread alternative to a timer. Tests a 'carriage return' in the file for a paragraph:
const
UM_MEMOCHAR = WM_USER + 22;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure UMMemoChar(var Msg: TMessage); message UM_MEMOCHAR;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TCharSender = class(TThread)
private
FCharWait, FParWait: Integer;
FFormHandle: HWND;
FFS: TFileStream;
protected
procedure Execute; override;
public
constructor Create(FileName: string; CharWait, ParagraphWait: Integer;
FormHandle: HWND);
destructor Destroy; override;
end;
constructor TCharSender.Create(FileName: string; CharWait, ParagraphWait: Integer;
FormHandle: HWND);
begin
FCharWait := CharWait;
FParWait := ParagraphWait;
FFormHandle := FormHandle;
FFS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
FreeOnTerminate := True;
inherited Create(False);
end;
destructor TCharSender.Destroy;
begin
FFS.Free;
inherited;
end;
procedure TCharSender.Execute;
var
C: Char;
begin
while (FFS.Position < FFS.Size) and not Terminated do begin
FFS.Read(C, SizeOf(C));
if (C <> #10) then
PostMessage(FFormHandle, UM_MEMOCHAR, Ord(C), 0);
if C = #13 then
Sleep(FParWait)
else
Sleep(FCharWait);
end;
end;
{TForm1}
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Clear;
TCharSender.Create(
ExtractFilePath(Application.ExeName) + 'text.txt', 20, 1000, Handle);
end;
procedure TForm1.UMMemoChar(var Msg: TMessage);
begin
Memo1.SelStart := Memo1.Perform(WM_GETTEXTLENGTH, 0, 0);
Memo1.Perform(WM_CHAR, Msg.WParam, 0);
end;
There's lots of ways to do this, and I'm not sure how you intend to handle newlines. However, all routes lead to TMemo.Lines which is a TStrings instance that wraps up the windows messages needed to interact with the underlying Windows edit control.
For example, these routines should get you started.
procedure AddNewLine(Memo: TMemo);
begin
Memo.Lines.Add('');
end;
procedure AddCharacter(Memo: TMemo; const C: Char);
var
Lines: TStrings;
begin
Lines := Memo.Lines;
if Lines.Count=0 then
AddNewLine(Memo);
Lines[Lines.Count-1] := Lines[Lines.Count-1]+C;
end;

Resources