keypad popup blocks view of application in delphi xe8 Firemonkey - delphi

I tried this example. But it doesn't work if the edit field has 'password' property set to true. Any idea how to make it work with password edit fields?

Here's a sample for what you are trying to do. Simply put all your controls inside lyVKMain.
object frmBaseForm: TfrmBaseForm
OnFocusChanged = FormFocusChanged
OnVirtualKeyboardShown = FormVirtualKeyboardShown
OnVirtualKeyboardHidden = FormVirtualKeyboardHidden
DesignerMasterStyle = 0
object vsVKScrollBox: TVertScrollBox
Align = Contents
Size.PlatformDefault = False
TabOrder = 2
Viewport.Width = 640.000000000000000000
Viewport.Height = 480.000000000000000000
object lyVKMain: TLayout
Align = Client
Size.PlatformDefault = False
TabOrder = 0
end
end
end
And the unit file...
unit uForm;
interface
uses
System.SysUtils,
System.Types,
System.UITypes,
System.Classes,
System.Variants,
FMX.Types,
FMX.Controls,
FMX.Forms,
FMX.Graphics,
FMX.Dialogs,
FMX.Layouts;
type
TfrmBaseForm = class(TForm)
vsVKScrollBox: TVertScrollBox;
lyVKMain: TLayout;
procedure FormFocusChanged(Sender: TObject);
procedure FormVirtualKeyboardHidden(Sender: TObject; KeyboardVisible: Boolean; const Bounds: TRect);
procedure FormVirtualKeyboardShown(Sender: TObject; KeyboardVisible: Boolean; const Bounds: TRect);
private
{ Private declarations }
public
{ Public declarations }
FKBBounds: TRectF;
FNeedOffset: Boolean;
procedure CalcContentBoundsProc(Sender: TObject; var ContentBounds: TRectF);
procedure RestorePosition;
procedure UpdateKBBounds;
constructor Create(AOwner: TComponent); override;
end;
var
frmBaseForm: TfrmBaseForm;
implementation
uses
System.Math;
{$R *.fmx}
{ TfrmBaseForm }
procedure TfrmBaseForm.CalcContentBoundsProc(Sender: TObject; var ContentBounds: TRectF);
begin
if FNeedOffset and (FKBBounds.Top > 0) then
begin
ContentBounds.Bottom := Max(ContentBounds.Bottom, 2 * ClientHeight - FKBBounds.Top);
end;
end;
constructor TfrmBaseForm.Create(AOwner: TComponent);
begin
inherited;
vsVKScrollBox.OnCalcContentBounds := CalcContentBoundsProc;
end;
procedure TfrmBaseForm.FormFocusChanged(Sender: TObject);
begin
UpdateKBBounds;
end;
procedure TfrmBaseForm.FormVirtualKeyboardHidden(Sender: TObject; KeyboardVisible: Boolean; const Bounds: TRect);
begin
FKBBounds.Create(0, 0, 0, 0);
FNeedOffset := False;
RestorePosition;
end;
procedure TfrmBaseForm.FormVirtualKeyboardShown(Sender: TObject; KeyboardVisible: Boolean; const Bounds: TRect);
begin
FKBBounds := TRectF.Create(Bounds);
FKBBounds.TopLeft := ScreenToClient(FKBBounds.TopLeft);
FKBBounds.BottomRight := ScreenToClient(FKBBounds.BottomRight);
UpdateKBBounds;
end;
procedure TfrmBaseForm.RestorePosition;
begin
vsVKScrollBox.ViewportPosition := PointF(vsVKScrollBox.ViewportPosition.X, 0);
lyVKMain.Align := TAlignLayout.Client;
vsVKScrollBox.RealignContent;
end;
procedure TfrmBaseForm.UpdateKBBounds;
var
LFocused: TControl;
LFocusRect: TRectF;
begin
FNeedOffset := False;
if Assigned(Focused) then
begin
LFocused := TControl(Focused.GetObject);
LFocusRect := LFocused.AbsoluteRect;
LFocusRect.Offset(vsVKScrollBox.ViewportPosition);
if (LFocusRect.IntersectsWith(TRectF.Create(FKBBounds))) and (LFocusRect.Bottom > FKBBounds.Top) then
begin
FNeedOffset := True;
lyVKMain.Align := TAlignLayout.Horizontal;
vsVKScrollBox.RealignContent;
Application.ProcessMessages;
vsVKScrollBox.ViewportPosition := PointF(vsVKScrollBox.ViewportPosition.X, LFocusRect.Bottom - FKBBounds.Top);
end;
end;
if not FNeedOffset then
RestorePosition;
end;
end.

Related

How can I enable a vetical scrollbar in a owner drawn listbox with items of variable height?

Recently, I was facing the following problem: a given listbox with the style lbOwnerDrawVariable that contains only one element doesn't display a vertical scrollbar, even if one is required. The text is truncated. How can I enable that scrollbar? Text height is measured by DrawText(..) with DT_CALCRECT and it returns a reasonable value, but no scrollbar appears.
Sample screenshot:
Here is a small example:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, System.StrUtils, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TOwnerDrawnListboxItem = class
AText: string;
end;
TOwnerDrawnListbox = class(TListbox)
protected
procedure Resize; override;
procedure DrawItem(Index: Integer; DisplayRect: TRect; State: TOwnerDrawState); override;
procedure MeasureItem(Index: Integer; var Height: Integer); override;
const
TEXT_FORMAT = DT_LEFT or DT_TOP or DT_NOPREFIX;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
FBox: TOwnerDrawnListbox;
FData: TOwnerDrawnListboxItem;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
Width := 250;
Height := 100;
FData := TOwnerDrawnListboxItem.Create();
for I := 10 to 30 do
FData.AText := FData.AText + DupeString(I.ToString(), 80) + sLinebreak;
FBox := TOwnerDrawnListbox.Create(Self);
FBox.Parent := Self;
FBox.Align := alClient;
FBox.Style := lbOwnerDrawVariable;
FBox.Items.AddObject('-', FData);
FBox.Resize;
end;
procedure TOwnerDrawnListbox.DrawItem(Index: Integer; DisplayRect: TRect; State: TOwnerDrawState);
var
LData: TOwnerDrawnListboxItem;
LTextRect: TRect;
begin
if (Index >= count) or (Items.Objects[Index] = nil) then
Exit;
LData := Items.Objects[Index] as TOwnerDrawnListboxItem;
LTextRect := DisplayRect;
Canvas.Font.Color := clBlack;
Canvas.Brush.Color := clMoneygreen;
Canvas.FillRect(DisplayRect);
InflateRect(LTextRect, - 2, - 2);
OffsetRect(LTextRect, 2, 2);
DrawText(Canvas.Handle, PChar(LData.AText), - 1, LTextRect, TEXT_FORMAT);
end;
procedure TOwnerDrawnListbox.MeasureItem(Index: Integer; var Height: Integer);
var
LTextRect: TRect;
LLineHeight: Integer;
LScrollWidth: Integer;
LPadding: Integer;
LData: TOwnerDrawnListboxItem;
LHeight: Integer;
begin
if (Index >= count) or (Items.Objects[Index] = nil) then
begin
Height := 1;
Exit;
end;
LData := Items.Objects[Index] as TOwnerDrawnListboxItem;
LLineHeight := Canvas.TextExtent('Gg').cy;
LScrollWidth := GetSystemMetrics(SM_CXVSCROLL);
LPadding := 4;
LTextRect := Rect(0, 0, ClientWidth - LPadding - LScrollWidth, LLineHeight);
LHeight := DrawText(Canvas.Handle, PChar(LData.AText), - 1, LTextRect, TEXT_FORMAT or DT_CALCRECT);
Height := LHeight + LPadding;
end;
procedure TOwnerDrawnListbox.Resize;
var
I: Integer;
NewHeight: Integer;
begin
for I := 0 to Pred(Items.count) do
begin
MeasureItem(I, NewHeight);
SendMessage(Self.Handle, LB_SETITEMHEIGHT, I, NewHeight);
end;
inherited;
Repaint;
end;
end.

Firemonkey Edit/Combo autocomplete/autosuggest while typing

What is the way to implement Autocomplete or Autosuggest with Delphi/Firemonkey for Windows/Android platforms as well as MacOS and iOS?
Example
When user types text in Google search box - some quick suggestions are shown.
There are lots of implementations for VCL with IAutoComplete, but there are less for FMX. What is needed is - FMX
I've made some research and compiled from different sources what I got below.
I've tested this on XE7/XE8 with Firemonkey. Perfectly runnig on Win32, Android and pretty sure MacOS.
I used to call suggestions within a timer, but the code below comes without a timer. The procedure to call in a timer or a thread is TStyledSuggestEdit.DropDownRecalc.
unit FMX.Edit.Suggest2;
interface
uses
FMX.Edit.Style, FMX.Controls.Presentation, FMX.Controls.Model, FMX.Presentation.Messages, FMX.Edit,
FMX.Controls, FMX.ListBox, System.Classes, System.Types;
const
PM_DROP_DOWN = PM_EDIT_USER + 10;
PM_PRESSENTER = PM_EDIT_USER + 11;
PM_SET_ITEMINDEX = PM_EDIT_USER + 12;
PM_GET_ITEMINDEX = PM_EDIT_USER + 13;
PM_GET_SELECTEDITEM = PM_EDIT_USER + 14;
PM_SET_ITEMCHANGE_EVENT = PM_EDIT_USER + 15;
PM_GET_ITEMS = PM_EDIT_USER + 16;
type
TSelectedItem = record
Text: String;
Data: TObject;
end;
TStyledSuggestEdit = class(TStyledEdit)
private
FItems: TStrings;
FPopup: TPopup;
FListBox: TListBox;
FDropDownCount: Integer;
FOnItemChange: TNotifyEvent;
FItemIndex: integer;
FDontTrack: Boolean;
FLastClickedIndex: Integer;
function _GetIndex: Integer;
procedure _SetIndex(const Value: Integer);
procedure _SetItems(const Value: TStrings);
protected
procedure CheckIfTextMatchesSuggestions; // used to find out if a typed text matches any of suggestions and then do select
function GetListBoxIndexByText(const AText: string): Integer;
procedure OnItemClick(const Sender: TCustomListBox; const Item: TListBoxItem);
procedure PMDropDown(var AMessage: TDispatchMessage); message PM_DROP_DOWN;
procedure MMDataChanged(var AMessage: TDispatchMessageWithValue<TDataRecord>); message MM_DATA_CHANGED;
procedure PMSetSize(var AMessage: TDispatchMessageWithValue<TSizeF>); message PM_SET_SIZE;
procedure PMGetItemIndex(var AMessage: TDispatchMessageWithValue<Integer>); message PM_GET_ITEMINDEX;
procedure PMSetItemIndex(var AMessage: TDispatchMessageWithValue<Integer>); message PM_SET_ITEMINDEX;
procedure PMGetItems(var AMessage: TDispatchMessageWithValue<TStrings>); message PM_GET_ITEMS;
procedure PMGetSelectedItem(var AMEssage: TDispatchMessageWithValue<TSelectedItem>); message PM_GET_SELECTEDITEM;
procedure PMSetItemChangeEvent(var AMessage: TDispatchMessageWithValue<TNotifyEvent>); message PM_SET_ITEMCHANGE_EVENT;
procedure PMPressEnter(var AMessage: TDispatchMessage); message PM_PRESSENTER;
procedure DoChangeTracking; override;
procedure RebuildSuggestionList(AText: String);
procedure RecalculatePopupHeight;
procedure KeyDown(var Key: Word; var KeyChar: Char; Shift: TShiftState); override;
procedure DropDownRecalc(ByText: string; Delay: integer = 100); //Delay parameter is a preparation for calling by a thread or a timer
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function _SelectedItem: TSelectedItem;
property _Items: TStrings read FItems write _SetItems;
property _ItemIndex: Integer read _GetIndex write _SetIndex;
property _OnItemChange: TNotifyEvent read FOnItemChange write FOnItemChange;
end;
TStyleSuggestEditProxy = class(TPresentationProxy)
protected
function CreateReceiver: TObject; override;
end;
TEditSuggestHelper = class helper for TEdit
public type
private
function GetIndex: Integer;
procedure SetIndex(const Value: Integer);
procedure SetOnItemChange(const Value: TNotifyEvent);
function GetItems: TStrings;
public
procedure AssignItems(const S: TStrings);
procedure ForceDropDown;
procedure PressEnter;
function SelectedItem: TSelectedItem;
property OnItemChange: TNotifyEvent write SetOnItemChange;
property ItemIndex: Integer read GetIndex write SetIndex;
property Items: TStrings read GetItems;
end;
implementation
uses
FMX.Presentation.Factory, FMX.Types, System.SysUtils, System.Math, System.Rtti, uDsTimers.FMX, {$IFDEF MSWINDOWS}Winapi.Windows,{$ENDIF}
System.UITypes;
{ TStyleSuggestEditProxy }
function TStyleSuggestEditProxy.CreateReceiver: TObject;
begin
Result := TStyledSuggestEdit.Create(nil);
end;
{ TStyledSuggestEdit }
procedure TStyledSuggestEdit.CheckIfTextMatchesSuggestions;
var I: integer;
begin
if FItemIndex = -1 then
begin
I := self.GetListBoxIndexByText(Edit.Text);
if I <> -1 then
try
OnItemClick(nil, FListBox.ListItems[I]); //try-except: maybe missing items if calling from a timer event or within a thread
FListBox.RemoveObject(FListBox.ListItems[I]);
RecalculatePopupHeight;
except
end;
end;
end;
constructor TStyledSuggestEdit.Create(AOwner: TComponent);
begin
inherited;
FItems := TStringList.Create;
FItemIndex := -1;
FPopup := TPopup.Create(self);
FPopup.Parent := Self;
FPopup.PlacementTarget := Self;
FPopup.Placement := TPlacement.Bottom;
FPopup.Width := Width;
FListBox := TListBox.Create(self);
FListBox.Parent := FPopup;
FListBox.Align := TAlignLayout.Client;
FListBox.OnItemClick := OnItemClick;
FDropDownCount := 5;
FListBox.Width := Self.Width;
FPopup.Width := Self.Width;
FLastClickedIndex := -1;
end;
destructor TStyledSuggestEdit.Destroy;
begin
FPopup := nil;
FListBox := nil;
FItems.Free;
inherited;
end;
procedure TStyledSuggestEdit.DoChangeTracking;
begin
inherited;
if Edit.Text <> _SelectedItem.Text then
FLastClickedIndex := -1;
if not FDontTrack and (FLastClickedIndex = -1) then
begin
_ItemIndex := -1;
DropDownRecalc(Edit.Text);
end;
end;
function TStyledSuggestEdit.GetListBoxIndexByText(const AText: string): Integer;
begin
for Result := 0 to FListBox.Count - 1 do
if FListBox.ListItems[Result].Text.ToLower = AText.ToLower then
Exit;
Result := -1;
end;
function TStyledSuggestEdit._GetIndex: Integer;
begin
Result := FItemIndex;
end;
procedure TStyledSuggestEdit.KeyDown(var Key: Word; var KeyChar: Char; Shift: TShiftState);
begin
inherited;
case Key of
vkReturn:
if FListBox.Selected <> nil then
begin
OnItemClick(FListBox, FListBox.Selected);
end;
vkEscape: FPopup.IsOpen := False;
vkDown: begin
if FListBox.Selected <> nil then
FListBox.ItemIndex := Min(FListBox.Count - 1, FListBox.ItemIndex + 1)
else
if FListBox.Count > 0 then
FListBox.ItemIndex := 0;
end;
vkUp: begin
if FListBox.Selected <> nil then
FListBox.ItemIndex := Max(0, FListBox.ItemIndex - 1);
end;
end;
if Assigned(OnKeyDown) then
OnKeyDown(Edit, Key, KeyChar, Shift);
end;
procedure TStyledSuggestEdit.MMDataChanged(var AMessage: TDispatchMessageWithValue<TDataRecord>);
var
Data: TDataRecord;
begin
Data := AMessage.Value;
if Data.Value.IsType <TStrings> and (Data.Key = 'Suggestions') then
FItems.Assign(Data.Value.AsType<TStrings>)
end;
procedure TStyledSuggestEdit.OnItemClick(const Sender: TCustomListBox; const Item: TListBoxItem);
begin
FLastClickedIndex := Item.Tag;
_ItemIndex := Item.Tag;
FPopup.IsOpen := Sender = nil; // whenever OnItemClick is being called programmatically `Sender` must be passed as `nil`,
Edit.SetFocus; // otherwise considered as real-user-click and should close popup
end;
procedure TStyledSuggestEdit.PMPressEnter(var AMessage: TDispatchMessage);
var K: word; KC: Char;
begin
K := vkReturn;
KC := #13;
KeyDown(K, KC, []);
end;
procedure TStyledSuggestEdit.PMDropDown(var AMessage: TDispatchMessage);
begin
inherited;
DropDownRecalc('',10);
end;
procedure TStyledSuggestEdit.PMGetItemIndex(var AMessage: TDispatchMessageWithValue<Integer>);
begin
AMessage.Value := self._ItemIndex;
end;
procedure TStyledSuggestEdit.PMGetItems(var AMessage: TDispatchMessageWithValue<TStrings>);
begin
AMessage.Value := Self._Items;
end;
procedure TStyledSuggestEdit.PMGetSelectedItem(var AMEssage: TDispatchMessageWithValue<TSelectedItem>);
begin
AMEssage.Value := self._SelectedItem;
end;
procedure TStyledSuggestEdit.PMSetItemChangeEvent(var AMessage: TDispatchMessageWithValue<TNotifyEvent>);
begin
FOnItemChange := AMessage.Value;
end;
procedure TStyledSuggestEdit.PMSetItemIndex(var AMessage: TDispatchMessageWithValue<Integer>);
begin
self._ItemIndex := AMessage.Value;
end;
procedure TStyledSuggestEdit.PMSetSize(var AMessage: TDispatchMessageWithValue<TSizeF>);
begin
inherited;
FPopup.Width := Width;
end;
procedure TStyledSuggestEdit.RebuildSuggestionList(AText: String);
var
i: integer;
Word: string;
begin
FListBox.Clear;
FListBox.BeginUpdate;
AText := AText.ToLower;
try
for i := 0 to FItems.Count - 1 do
if AText.IsEmpty or FItems[i].ToLower.StartsWith(AText) then
begin
FListBox.AddObject(TListBoxItem.Create(FListBox));
FListBox.ListItems[FListBox.Count - 1].Tag := I;
FListBox.ListItems[FListBox.Count - 1].Data := FItems.Objects[i];
FListBox.ListItems[FListBox.Count - 1].Text := FItems[i];
end;
finally
FListBox.EndUpdate;
end;
end;
procedure TStyledSuggestEdit.RecalculatePopupHeight;
begin
if FListBox.Items.Count > 0 then
begin
FPopup.Height := FListBox.ListItems[0].Height * Min(FDropDownCount, FListBox.Items.Count) + FListBox.BorderHeight;
FPopup.PopupFormSize := TSizeF.Create(FPopup.Width, FPopup.Height);
end
else
begin
FPopup.Height := 1; // instead this it's possible to hide FPopup.IsOpen := false;
FPopup.PopupFormSize := TSizeF.Create(FPopup.Width, FPopup.Height);
end;
end;
function TStyledSuggestEdit._SelectedItem: TSelectedItem;
begin
if FItemIndex = -1 then
begin
Result.Text := '';
Result.Data := nil;
end
else
begin
Result.Text := FItems[FItemIndex];
Result.Data := FItems.Objects[FItemIndex];
end;
end;
procedure TStyledSuggestEdit._SetIndex(const Value: Integer);
begin
if (Value >= -1) and (Value < FItems.Count) and (Value <> FItemIndex) then
begin
FDontTrack := true;
FItemIndex := Value;
if (FItemIndex >= 0) and (Edit.Text <> _SelectedItem.Text) then
begin
Edit.Text := _SelectedItem.Text;
Edit.GoToTextEnd;
end;
if Assigned(FOnItemChange) then
FOnItemChange(Edit);
FDontTrack := false;
end;
end;
procedure TStyledSuggestEdit._SetItems(const Value: TStrings);
begin
FItems := Value;
_ItemIndex := -1;
end;
procedure TStyledSuggestEdit.DropDownRecalc(ByText: string; Delay: integer);
begin
// Here is possible to use a timer call or a call in a thread;
if not self.FDontTrack then
begin
Self.RebuildSuggestionList(ByText);
Self.RecalculatePopupHeight;
self.FPopup.IsOpen := self.FListBox.Items.Count > 0;
CheckIfTextMatchesSuggestions;
end;
end;
{ TEditHelper }
procedure TEditSuggestHelper.PressEnter;
begin
if HasPresentationProxy then
PresentationProxy.SendMessage(PM_PRESSENTER);
end;
function TEditSuggestHelper.SelectedItem: TSelectedItem;
begin
if HasPresentationProxy then
PresentationProxy.SendMessageWithResult<TSelectedItem>(PM_GET_SELECTEDITEM, Result);
end;
procedure TEditSuggestHelper.SetIndex(const Value: Integer);
begin
if HasPresentationProxy then
PresentationProxy.SendMessage<Integer>(PM_SET_ITEMINDEX, Value);
end;
procedure TEditSuggestHelper.SetOnItemChange(const Value: TNotifyEvent);
begin
if HasPresentationProxy then
PresentationProxy.SendMessage<TNotifyEvent>(PM_SET_ITEMCHANGE_EVENT, Value);
end;
procedure TEditSuggestHelper.ForceDropDown;
begin
if HasPresentationProxy then
PresentationProxy.SendMessage(PM_DROP_DOWN);
end;
function TEditSuggestHelper.GetIndex: Integer;
begin
if HasPresentationProxy then
PresentationProxy.SendMessageWithResult<Integer>(PM_GET_ITEMINDEX, Result);
end;
function TEditSuggestHelper.GetItems: TStrings;
begin
if HasPresentationProxy then
PresentationProxy.SendMessageWithResult<TStrings>(PM_GET_ITEMS, Result);
end;
procedure TEditSuggestHelper.AssignItems(const S: TStrings);
begin
self.Model.Data['Suggestions'] := TValue.From<TStrings>(S);
end;
initialization
TPresentationProxyFactory.Current.Register('SuggestEditStyle', TStyleSuggestEditProxy);
finalization
TPresentationProxyFactory.Current.Unregister('SuggestEditStyle');
end.
Here is how you use it:
Create Multi-Device application
On a HD form place common TEdit component
Define for TEdit.OnPresentationNameChoosing on Events tab the following:
procedure TForm1.Edit1PresentationNameChoosing(Sender: TObject; var PresenterName: string);
begin
inherited;
PresenterName := 'SuggestEditStyle';
end;
Add items to your sl: TStrings by: sl.AddObject('Name', TIntObj.Create(10));
Assign sl: TStrings to your Edit by: Edit1.AssignItems(sl);
Comment out TStyledSuggestEdit.CheckIfTextMatchesSuggestions in the code if you don't need Autoselect ability while typing.
Test Form1
Form reference
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 325
ClientWidth = 225
FormFactor.Width = 320
FormFactor.Height = 480
FormFactor.Devices = [Desktop]
OnCreate = FormCreate
DesignerMasterStyle = 0
object Edit1: TEdit
Touch.InteractiveGestures = [LongTap, DoubleTap]
Align = Top
TabOrder = 0
OnPresentationNameChoosing = Edit1PresentationNameChoosing
Position.X = 20.000000000000000000
Position.Y = 57.000000000000000000
Margins.Left = 20.000000000000000000
Margins.Right = 20.000000000000000000
Size.Width = 185.000000000000000000
Size.Height = 22.000000000000000000
Size.PlatformDefault = False
object Button2: TButton
Align = Right
Cursor = crArrow
Margins.Left = 1.000000000000000000
Margins.Top = 1.000000000000000000
Margins.Right = 1.000000000000000000
Margins.Bottom = 1.000000000000000000
Position.X = 156.500000000000000000
Position.Y = 0.500000000000000000
Scale.X = 0.500000000000000000
Scale.Y = 0.500000000000000000
Size.Width = 56.000000000000000000
Size.Height = 42.000000000000000000
Size.PlatformDefault = False
StyleLookup = 'arrowdowntoolbutton'
TabOrder = 0
Text = 'Button2'
OnClick = Button2Click
end
end
object Button1: TButton
Align = Top
Margins.Left = 30.000000000000000000
Margins.Top = 10.000000000000000000
Margins.Right = 30.000000000000000000
Position.X = 30.000000000000000000
Position.Y = 89.000000000000000000
Size.Width = 165.000000000000000000
Size.Height = 22.000000000000000000
Size.PlatformDefault = False
TabOrder = 1
Text = 'Set 3rd item'
OnClick = Button1Click
end
object Label1: TLabel
Align = Top
Size.Width = 225.000000000000000000
Size.Height = 57.000000000000000000
Size.PlatformDefault = False
Text = 'Label1'
end
end
Code reference
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.Edit, FMX.Edit.Suggest2, FMX.Layouts, FMX.ListBox,
FMX.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Button2: TButton;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure esItemChange(Sender: TObject);
procedure Edit1PresentationNameChoosing(Sender: TObject; var PresenterName: string);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
sl: TStrings;
implementation
{$R *.fmx}
type
TIntObj = class(TObject)
private
FId: integer;
public
constructor Create(Id: integer); overload;
function Value: integer;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.ItemIndex := 3; // force choice as if it was combobox behaviour
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Edit1.ForceDropDown; // add a button inside TEdit and use it as dropdown
end;
procedure TForm1.Edit1PresentationNameChoosing(Sender: TObject; var PresenterName: string);
begin
inherited;
PresenterName := 'SuggestEditStyle';
end;
procedure TForm1.esItemChange(Sender: TObject);
begin
// occurs when ItemIndex is changed
Label1.Text := TEdit(Sender).SelectedItem.Text + LineFeed + 'idx=' + TEdit(Sender).ItemIndex.ToString + LineFeed + 'data=';
if TEdit(Sender).SelectedItem.Data <> nil then
Label1.Text := Label1.Text + TIntObj(TEdit(Sender).SelectedItem.Data).Value.ToString
else
Label1.Text := Label1.Text + 'nil';
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
sl := TStringList.Create;
//sl.AddObject('aaa',10); // Segmentation fault 11 under Android
sl.AddObject('aaa',TIntObj.Create(10));
sl.AddObject('aaabb',TIntObj.Create(20));
sl.AddObject('aaabbbcc',TIntObj.Create(30));
sl.AddObject('aaacc',TIntObj.Create(40));
sl.AddObject('aaafff',TIntObj.Create(50));
sl.AddObject('aaaggg',TIntObj.Create(60));
Edit1.AssignItems(sl);
Edit1.OnItemChange := esItemChange;
end;
{ TIntObject }
constructor TIntObj.Create(Id: integer);
begin
inherited Create;
FId := Id;
end;
function TIntObj.Value: integer;
begin
Result := FId;
end;
end.
Tested Win32 [Windows 7/8] and Android 4.4.4 device [MI3W]
Hope this helps. Any further ideas and suggestions are appreciated.
In the previous answer for Delphi XE10 change line
Result := TStyledSuggestEdit.Create(nil);
to
Result := TStyledSuggestEdit.Create(nil, Model, PresentedControl);
in the function TStyleSuggestEditProxy.CreateReceiver: TObject;
Plus change Data.Key = 'Suggestions' to Data.Key = 'suggestions' in the TStyledSuggestEdit.MMDataChanged
For iOS (I did not check on Android, but should also work) set ControlType of TMemo or TEdit to Platform - this will show T9 autocomplete and check spelling.

How To Record A Video And Audio Through Device, Firemonkey?

My Code for video recording is given, the recording is not in a smooth way i.e. the place where I turn my camera appears on the preview view late. How I can resolve this issue
unit VideoAttachmentUnit;
interface
uses
System.SysUtils,
System.Types,
System.UITypes,
System.Classes,
System.Variants,
FMX.Types,
FMX.Controls,
FMX.Forms,
FMX.Dialogs,
FMX.StdCtrls,
FMX.Media,
FMX.Platform,
FMX.Objects,
FMX.Layouts,
FMX.Memo,
FMX.Controls.Presentation;
type
TVideoAttachmentForm = class(TForm)
NavBar: TToolBar;
CameraChangeBtn: TButton;
PlayBtn: TButton;
CloseScreenBtn: TButton;
ToolBar1: TToolBar;
StartRecordingBtn: TButton;
StopRecordingBtn: TButton;
ImageCameraView: TImage;
CameraComponent: TCameraComponent;
procedure FormCreate(Sender: TObject);
procedure CloseScreenBtnClick(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure CameraChangeBtnClick(Sender: TObject);
procedure StartRecordingBtnClick(Sender: TObject);
procedure StopRecordingBtnClick(Sender: TObject);
procedure CameraComponentSampleBufferReady(Sender: TObject;
const ATime: TMediaTime);
private
{ Private declarations }
procedure GetImage;
procedure InitialSettingsForTheRecording;
public
function AppEvent(AAppEvent: TApplicationEvent; AContext: TObject): Boolean;
end;
var
VideoAttachmentForm: TVideoAttachmentForm;
WhichCamera:String;
procedure DisplayTheVideoAttachmentScreen;
implementation
{$R *.fmx}
procedure DisplayTheVideoAttachmentScreen;
begin
try
Application.CreateForm(TVideoAttachmentForm , VideoAttachmentForm);
VideoAttachmentForm.Show;
finally
end;
end;
procedure TVideoAttachmentForm.CameraChangeBtnClick(Sender: TObject);
var
LActive: Boolean;
begin
{ Select Back Camera }
LActive := CameraComponent.Active;
try
CameraComponent.Active := False;
if WhichCamera = 'BackCamera' then
begin
CameraComponent.Kind := TCameraKind.FrontCamera;
WhichCamera := 'FrontCamera';
end
else if WhichCamera = 'FrontCamera' then
begin
CameraComponent.Kind := TCameraKind.BackCamera;
WhichCamera := 'BackCamera';
end;
finally
CameraComponent.Active := LActive;
end;
end;
procedure TVideoAttachmentForm.CameraComponentSampleBufferReady(Sender: TObject;
const ATime: TMediaTime);
begin
TThread.Synchronize(TThread.CurrentThread, GetImage);
ImageCameraView.Width := ImageCameraView.Bitmap.Width;
ImageCameraView.Height := ImageCameraView.Bitmap.Height;
end;
procedure TVideoAttachmentForm.CloseScreenBtnClick(Sender: TObject);
begin
VideoAttachmentForm.Close;
end;
procedure TVideoAttachmentForm.FormCreate(Sender: TObject);
var
AppEventSvc: IFMXApplicationEventService;
begin
if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(AppEventSvc)) then
AppEventSvc.SetApplicationEventHandler(AppEvent);
end;
procedure TVideoAttachmentForm.FormShow(Sender: TObject);
begin
InitialSettingsForTheRecording;
end;
function TVideoAttachmentForm.AppEvent(AAppEvent: TApplicationEvent; AContext: TObject): Boolean;
begin
case AAppEvent of
TApplicationEvent.WillBecomeInactive:
CameraComponent.Active := False;
TApplicationEvent.EnteredBackground:
CameraComponent.Active := False;
TApplicationEvent.WillTerminate:
CameraComponent.Active := False;
end;
end;
procedure TVideoAttachmentForm.InitialSettingsForTheRecording;
var
LSettings: TVideoCaptureSetting;
begin
CameraComponent.Kind := TCameraKind.BackCamera;
WhichCamera := 'BackCamera';
if CameraComponent.HasTorch then
begin
CameraComponent.TorchMode := TTorchMode.ModeAuto;
end;
CameraComponent.Quality := TVideoCaptureQuality.CaptureSettings;
CameraComponent.CaptureSettingPriority := TVideoCaptureSettingPriority.FrameRate;
end;
procedure TVideoAttachmentForm.StartRecordingBtnClick(Sender: TObject);
begin
CameraComponent.Active := True;
end;
procedure TVideoAttachmentForm.StopRecordingBtnClick(Sender: TObject);
begin
CameraComponent.Active := False;
end;
procedure TVideoAttachmentForm.GetImage;
begin
CameraComponent.SampleBufferToBitmap(ImageCameraView.Bitmap, True);
end;
end.

Delphi Created Images are not displayed

I am trying to dynamiclly create a custom component with images and display them in a Grid , but the Images don't show up. Below is the code with omitted part of declarations , could someone help me and tell me what am I doint wrong ?
Custom component Class
unit Tile;
interface
uses FMX.Controls, FMX.StdCtrls, System.Classes, FMX.Types, System.StrUtils ,
System.SysUtils, System.Types, System.UITypes,
System.Variants,
FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Ani,
FMX.Objects, FMX.Layouts;
type
TTileType = (Slider, Memory, Tile3D);
TTile = class
private
FOnChangedText: TNotifyEvent;
FType: TTileType;
FControl: TComponent;
FText: String;
FName: String;
FBitmap : TBitmap;
FAlign : TAlignLayout;
procedure TextChangedDefault(Sender: TObject);
protected
procedure SetText(aText: String);
procedure TextChanged; virtual;
procedure SetControlOnClick(AProc: TNotifyEvent);
function GetControlOnClick: TNotifyEvent;
procedure SetControlName(aName: String);
procedure SetBitmap(bitmap:TBitmap);
procedure SetAlign(align :TAlignLayout);
public
constructor Create(AParent: TFmxObject; AType: TTileType);
destructor Destroy; override;
published
property Text: String read FText write SetText;
property Name: String read FName write SetControlName;
property Bitmap:TBitmap read FBitmap write SetBitmap;
property Align:TAlignLayout read FAlign write SetAlign;
property OnChangedText: TNotifyEvent read FOnChangedText
write FOnChangedText;
property OnClick: TNotifyEvent read GetControlOnClick
write SetControlOnClick;
end;
implementation
constructor TTile.Create(AParent: TFmxObject; AType: TTileType);
begin
FType := AType;
case FType of
Slider:
begin
FControl := TButton.Create(AParent as TComponent);
FOnChangedText := TextChangedDefault;
(FControl as TFmxObject).Parent := AParent;
end;
Memory:
begin
FControl := TImage.Create(AParent as TComponent);
FOnChangedText := TextChangedDefault;
(FControl as TFmxObject).Parent := AParent;
end;
Tile3D:
FControl := nil;
else
FControl := nil;
end;
FName := FControl.Name;
end;
destructor TTile.Destroy;
begin
FControl.DisposeOf;
inherited;
end;
function TTile.GetControlOnClick: TNotifyEvent;
begin
case FType of
Slider:
begin
Result := (FControl as TButton).OnClick;
end;
Memory:
begin
Result := (FControl as TImage).OnClick;
end;
Tile3D:
begin
// TODO
end;
else
Result := nil;
end;
end;
procedure TTile.SetControlName(aName: String);
begin
FName := aName;
FControl.Name := aName;
end;
procedure TTile.SetBitmap(bitmap :TBitmap);
begin
FBitmap:=bitmap;
end;
procedure TTile.SetAlign(align :TAlignLayout);
begin
FAlign:=align;
end;
procedure TTile.SetControlOnClick(AProc: TNotifyEvent);
begin
case FType of
Slider:
begin
(FControl as TButton).OnClick := AProc;
end;
Memory:
begin
(FControl as TImage).OnClick := AProc;
end;
Tile3D:
begin
// TODO
end;
end;
end;
procedure TTile.SetText(aText: String);
begin
FText := aText;
TextChanged;
end;
procedure TTile.TextChanged;
begin
if Assigned(FOnChangedText) then
FOnChangedText(Self);
end;
procedure TTile.TextChangedDefault(Sender: TObject);
begin
(FControl as TButton).Text := FText;
end;
end.
Memory Game Class:
unit MemoryGame;
interface
uses Tile, Consts, FMX.Controls, FMX.StdCtrls, FMX.Layouts, System.Classes,
FMX.Types, System.Types, FMX.Graphics, System.SysUtils, FMX.Dialogs,Helper,FMX.ExtCtrls ,
System.UITypes,
System.Variants,
FMX.Forms,
FMX.TabControl, SliderPuzzle, System.Actions,
FMX.ActnList, FMX.StdActns, FMX.MultiView, FMX.Controls.Presentation, FMX.Edit,
DateUtils ,FMX.Objects ;
type
TMemoryGame = class(TGridLayout)
private
FTiles: TArray<TTile>;
procedure FillGrid(aTileNo: Integer);
protected
public
constructor Create(AParent: TFmxObject; aTileNo: Integer); reintroduce;
end;
var
moveCounter : Integer = 0 ;
implementation
{ MemoryGame }
constructor TMemoryGame.Create(AParent: TFmxObject; aTileNo: Integer);
begin
inherited Create(nil);
Parent := AParent;
FillGrid(aTileNo);
end;
procedure TMemoryGame.FillGrid(aTileNo: Integer);
var
I: Integer;
LTile: TTile;
begin
SetLength(FTiles, aTileNo);
for I := 0 to aTileNo - 1 do
begin
LTile := TTile.Create(Self, TTileType.Memory);
FTiles[I] := LTile;
if I = 0 then
begin
LTile.Bitmap:= TBitmap.CreateFromFile('../../img/img1.bmp');
LTile.Align := TAlignLayout.Client;
LTile.Align := TAlignLayout.Center;
end
else
begin
LTile.Bitmap:= TBitmap.CreateFromFile('../../img/img1.bmp');
LTile.Align := TAlignLayout.Client;
LTile.Align := TAlignLayout.Center;
end;
end;
end;
end.
Main Form:
unit MainForm;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes,
System.Variants, Consts,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, FMX.ExtCtrls,
FMX.Layouts, FMX.TabControl, SliderPuzzle, System.Actions,
FMX.ActnList, FMX.StdActns, FMX.MultiView, FMX.Controls.Presentation, FMX.Edit,
DateUtils,MemoryGame, FMX.Objects;
type
TFormMain = class(TForm)
tcMain: TTabControl;
ti1Slider: TTabItem;
ti2Runtime: TTabItem;
ti4Game3D: TTabItem;
ti3Memory: TTabItem;
GridLayout: TGridLayout;
bTile1: TButton;
bTile2: TButton;
bTile3: TButton;
bTile4: TButton;
bTile5: TButton;
bTile6: TButton;
bTile7: TButton;
bTile8: TButton;
bTile9: TButton;
bTile10: TButton;
bTile11: TButton;
bTile12: TButton;
bTile13: TButton;
bTile14: TButton;
bTile15: TButton;
bTileEmpty: TButton;
bNew: TButton;
MultiView: TMultiView;
bExitApp: TButton;
ActionList: TActionList;
FileExitActn: TFileExit;
NewGameActn: TAction;
StyleBook: TStyleBook;
hitCountLabel: TLabel;
movesCounter: TLabel;
TimeCountLabel: TLabel;
timer: TLabel;
Timer1: TTimer;
procedure bTileClick(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure NewGameActnExecute(Sender: TObject);
procedure GridLayoutResize(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
FormMain: TFormMain;
Slider: TSliderPuzzle;
Memory : TMemoryGame;
firstMove : Boolean = true;
stop, elapsed : TDateTime ;
start : TDateTime = 0 ;
implementation
{$R *.fmx}
procedure TFormMain.NewGameActnExecute(Sender: TObject);
begin
if ti1Slider.IsSelected then
repeat
begin
firstMove:=true;
Slider.ShuffleTiles(GridLayout);
Slider.resetMoveCounter;
Timer1.Enabled := true;
Timer1.Interval :=1000;
Slider.resetTimer(start);
movesCounter.Text := IntToStr(Slider.GetMoveCount);
timer.Text := '--/--/--';
end;
until not Slider.IsGameOver(GridLayout)
else if ti2Runtime.IsSelected then
repeat
Slider.ShuffleTiles
until not Slider.IsGameOver;
end;
procedure TFormMain.Timer1Timer(Sender: TObject);
var myVar:Integer;
begin
if start<>0 then
begin
myVar := SecondsBetween(start,Now);
timer.Text :=Format('%.2d:%.2d', [myVar div 60, myVar mod 60]); ;
end;
end;
procedure TFormMain.bTileClick(Sender: TObject);
begin
if firstMove then
begin
Slider.startCount(start);
firstMove:=false;
end;
Slider.incrementCounter;
movesCounter.Text := IntToStr(Slider.GetMoveCount);
Slider.SwapTiles(GridLayout, Sender as TButton, bTileEmpty);
if Slider.IsGameOver(GridLayout) then
begin
Slider.resetMoveCounter;
Slider.resetTimer(start);
// movesCounter.Text := IntToStr(Slider.GetMoveCount);
// timer.Text := '--/--/--';
Timer1.Enabled := false;
ShowMessage('GAME OVER');
firstMove:=true;
ti3Memory.Enabled := true;
ti3Memory.TabControl.SetActiveTabWithTransition(ti3Memory,TTabTransition.Slide);
end;
end;
procedure TFormMain.GridLayoutResize(Sender: TObject);
begin
GridLayout.ItemHeight := GridLayout.Height / COLS-25;
GridLayout.ItemWidth := GridLayout.Width / ROWS;
end;
procedure TFormMain.FormShow(Sender: TObject);
begin
ReportMemoryLeaksOnShutdown := true;
Slider := TSliderPuzzle.Create(Self.ti2Runtime, TILES);
Slider.Height := GridLayout.Height;
Slider.Width := GridLayout.Width;
Slider.Align := TAlignLayout.Client;
//PuzzleGame
ReportMemoryLeaksOnShutdown := true;
Memory := TMemoryGame.Create(Self.ti3Memory, TILES);
Memory.Height := GridLayout.Height;
Memory.Width := GridLayout.Width;
Memory.Align := TAlignLayout.Client;
end;
end.
Call the assign() method of the FBitmap variable inside youe Set procedure:
procedure TTile.SetBitmap(bitmap :TBitmap);
begin
FBitmap.Assign(bitmap);
end;
Adding the following code to Tile class , fixed the issues.
type
private
FOnChangedBitmap : TNotifyEvent;
protected
procedure BitmapChanged;virtual;
procedure TTile.BitmapChanged;
begin
if Assigned(FOnChangedBitmap) then
FOnChangedBitmap(Self);
end;
procedure TTile.BitmapChangedDefault(Sender: TObject);
begin
(FControl as TImage).Bitmap := FBitmap;
end;
procedure TTile.SetBitmap(bitmap :TBitmap);
begin
FBitmap:=bitmap;
BitmapChanged;
end;
This all looks very complicated and perhaps it is.
But I solved a similar problem by simply setting the parent of the image:
Fheart := TImage.Create(self);
Fheart.Parent := self;
Fheart.SetSubComponent(true);
It seems unneccessary setting the parent when that is passed as the owner in the constructor - but it did solve my problem

Firemonkey and TDownloadUrl

I have an (Delphi XE2) VCL app containing an object TDownloadUrl (VCL.ExtActns) to check several webpages, so I wonder if there is an equivalent object in FireMonkey, 'cause I wanna take advantage of rich features from this new platform.
A Firemonkey app demo using threads would appreciate. Thanks in advance.
Actions don't exist yet with FireMonkey.
BTW, you can create the same behavior with a code like this:
IdHTTP1: TIdHTTP;
...
procedure TForm2.MenuItem1Click(Sender: TObject);
const
FILENAME = 'C:\Users\Whiler\Desktop\test.htm';
URL = 'http://stackoverflow.com/questions/7491389/firemonkey-and-tdownloadurl';
var
// sSource: string;
fsSource: TFileStream;
begin
if FileExists(FILENAME) then
begin
fsSource := TFileStream.Create(FILENAME, fmOpenWrite);
end
else
begin
fsSource := TFileStream.Create(FILENAME, fmCreate);
end;
try
IdHTTP1.Get(URL, fsSource);
finally
fsSource.Free;
end;
// sSource := IdHTTP1.Get(URL);
end;
The commented lines can replace the others if you just need the source in memory...
If you want to use a thread, you can manage it like this:
unit Unit2;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP, FMX.Menus;
type
TDownloadThread = class(TThread)
private
idDownloader: TIdHTTP;
FFileName : string;
FURL : string;
protected
procedure Execute; override;
procedure Finished;
public
constructor Create(const sURL: string; const sFileName: string);
destructor Destroy; override;
end;
type
TForm2 = class(TForm)
MenuBar1: TMenuBar;
MenuItem1: TMenuItem;
procedure MenuItem1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.fmx}
procedure TForm2.MenuItem1Click(Sender: TObject);
const
FILENAME = 'C:\Users\Whiler\Desktop\test.htm';
URL = 'http://stackoverflow.com/questions/7491389/firemonkey-and-tdownloadurl';
var
// sSource: string;
fsSource: TFileStream;
begin
TDownloadThread.Create(URL, FILENAME).Start;
end;
{ TDownloadThread }
constructor TDownloadThread.Create(const sURL, sFileName: string);
begin
inherited Create(true);
idDownloader := TIdHTTP.Create(nil);
FFileName := sFileName;
FURL := sURL;
FreeOnTerminate := True;
end;
destructor TDownloadThread.Destroy;
begin
idDownloader.Free;
inherited;
end;
procedure TDownloadThread.Execute;
var
// sSource: string;
fsSource: TFileStream;
begin
inherited;
if FileExists(FFileName) then
begin
fsSource := TFileStream.Create(FFileName, fmOpenWrite);
end
else
begin
fsSource := TFileStream.Create(FFileName, fmCreate);
end;
try
idDownloader.Get(FURL, fsSource);
finally
fsSource.Free;
end;
Synchronize(Finished);
end;
procedure TDownloadThread.Finished;
begin
// replace by whatever you need
ShowMessage(FURL + ' has been downloaded!');
end;
end.
Regarding this:
A Firemonkey app demo using threads would appreciate.
You can find a FireMonkey demo which is using Thread here: https://radstudiodemos.svn.sourceforge.net/svnroot/radstudiodemos/branches/RadStudio_XE2/FireMonkey/FireFlow/MainForm.pas
type
TImageThread = class(TThread)
private
FImage: TImage;
FTempBitmap: TBitmap;
FFileName: string;
protected
procedure Execute; override;
procedure Finished;
public
constructor Create(const AImage: TImage; const AFileName: string);
destructor Destroy; override;
end;
...
TImageThread.Create(Image, Image.TagString).Start;
if you don't have this demo in your sample directory, you can check it out from the subversion repository used in the link above.
You can using this code.
unit BitmapHelperClass;
interface
uses
System.Classes, FMX.Graphics;
type
TBitmapHelper = class helper for TBitmap
public
procedure LoadFromUrl(AUrl: string);
procedure LoadThumbnailFromUrl(AUrl: string; const AFitWidth, AFitHeight: Integer);
end;
implementation
uses
System.SysUtils, System.Types, IdHttp, IdTCPClient, AnonThread;
procedure TBitmapHelper.LoadFromUrl(AUrl: string);
var
_Thread: TAnonymousThread<TMemoryStream>;
begin
_Thread := TAnonymousThread<TMemoryStream>.Create(
function: TMemoryStream
var
Http: TIdHttp;
begin
Result := TMemoryStream.Create;
Http := TIdHttp.Create(nil);
try
try
Http.Get(AUrl, Result);
except
Result.Free;
end;
finally
Http.Free;
end;
end,
procedure(AResult: TMemoryStream)
begin
if AResult.Size > 0 then
LoadFromStream(AResult);
AResult.Free;
end,
procedure(AException: Exception)
begin
end
);
end;
procedure TBitmapHelper.LoadThumbnailFromUrl(AUrl: string; const AFitWidth,
AFitHeight: Integer);
var
Bitmap: TBitmap;
scale: Single;
begin
LoadFromUrl(AUrl);
scale := RectF(0, 0, Width, Height).Fit(RectF(0, 0, AFitWidth, AFitHeight));
Bitmap := CreateThumbnail(Round(Width / scale), Round(Height / scale));
try
Assign(Bitmap);
finally
Bitmap.Free;
end;
end;
end.

Resources