Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Versuch: TFileBrowser für Android, geht das ohne globale Variable? (https://www.delphipraxis.net/206046-versuch-tfilebrowser-fuer-android-geht-das-ohne-globale-variable.html)

Renate Schaaf 12. Nov 2020 19:32

Versuch: TFileBrowser für Android, geht das ohne globale Variable?
 
Ich habe versucht, einen Ersatz für TFileOpenDialog zu bauen, der möglichst einfach für Android aufrufbar ist. Das geht aber nur, indem der Benutzer eine Callback-Funktion definiert, also ein "OnExecute", jedenfalls fällt mir nichts anderes ein. Damit der Aufruf möglichst zusammenhängend ist, kann man das Callback anonym definieren.

Hier ist ein Beispiel für den Aufruf:

Delphi-Quellcode:
procedure TForm1.BrowseForVideoFile;
begin
{$IFDEF ANDROID}
  TFileBrowser.BrowseForFile(ctVideo,
  // This procedure is called when the user has picked an item
    procedure(var filename: string; var success: boolean)
    begin
      if success then
      begin
        fVideoFilename := filename;
        Edit2.text := fVideoFilename;
        ProcessVideoFile;
      end
      else
        TDialogService.ShowMessage
          ('Pick the file by opening the Documents folder. Don''t use any of the other apps.');
    end);

{$ELSE}
...
end;
Dafür ist TFileBrowser.BrowseForFile eine class procedure. Bisher habe ich das nur ans Laufen bekommen, indem ich im Interface eine globale Variable vom Typ TFileBrowser definiere. Kann man das irgendwie schlauer machen?

Hier ist der source code für TFileBrowser, ist nicht so lang:

Delphi-Quellcode:
unit UAndroidTools;

interface

uses
  System.Messaging,
  AndroidAPI.Helpers,
  AndroidAPI.Jni.Os,
  AndroidAPI.Jni.GraphicsContentViewText,
  AndroidAPI.Jni.Net,
  FMX.Platform.Android,
  AndroidAPI.JNIBridge,
  AndroidAPI.Jni.JavaTypes;

type
  TContentEnum = (ctVideo, ctImages, ctAudio, ctText, ctAll);

  TFileBrowseEvent = reference to procedure(var Filename: string;
    var Success: boolean);

  TFileBrowser = class
  private
    fOnExecute: TFileBrowseEvent;
    fFilename: string;
    function HandleIntentAction(const Data: JIntent): boolean;
    procedure DoExecute(Success: boolean);
  protected
    fMessageSubscriptionID: integer;
    procedure HandleActivityMessage(const sender: TObject; const M: TMessage);
  public
    ///<summary> Use Android API to have the user pick a file with a certain content type </summary>
    /// <param OnExecute> Event occuring when the user has picked a file. Write a (anonymous) procedure(var Filename: string; var Success: boolean) to handle the event. <param>
    class procedure BrowseForFile(Content: TContentEnum;
      OnExecute: TFileBrowseEvent);
  end;

implementation


const
  ContentStrings: array [TContentEnum] of string = ('video/*', 'image/*',
    'audio/*', 'text/*', '*/*');

  { TFileBrowser }

var
  //Wie schafft man das ohne globale Variable?
  _TheFileBrowser: TFileBrowser;

class procedure TFileBrowser.BrowseForFile(Content: TContentEnum;
  OnExecute: TFileBrowseEvent);
var
  Intent: JIntent;
begin
  _TheFileBrowser := TFileBrowser.Create;
  //Das ist doch irgendwie doof.
  With _TheFileBrowser do
  begin
    fOnExecute := OnExecute;
    fMessageSubscriptionID := TMessageManager.DefaultManager.SubscribeToMessage
      (TMessageResultNotification, HandleActivityMessage);
    Intent := TJIntent.Create;
    Intent.setType(StringToJString(ContentStrings[Content]));
    Intent.setAction(TJIntent.JavaClass.ACTION_GET_CONTENT);
    MainActivity.startActivityForResult(Intent, 0);
  end;
end;

procedure TFileBrowser.DoExecute(Success: boolean);
begin
  if assigned(fOnExecute) then
    fOnExecute(fFilename, Success);
  self.disposeof;
end;

procedure TFileBrowser.HandleActivityMessage(const sender: TObject;
  const M: TMessage);
begin
  if M is TMessageResultNotification then
  begin
    DoExecute(HandleIntentAction(TMessageReceivedNotification(M).Value));
  end;
end;

function TFileBrowser.HandleIntentAction(const Data: JIntent): boolean;
var
  C: JCursor;
  I: integer;
begin
  C := MainActivity.getContentResolver.query(Data.getData, nil,
    StringToJString(''),
    nil, StringToJString(''));

  C.moveToFirst;
  Result := false;
  for I := 0 to C.getColumnCount - 1 do
  begin
    if JStringToString(C.getColumnName(I)) = '_data' then
    // '_data' column contains the path
    begin
      fFilename := JStringToString(C.getString(I));
      Result := true;
      Break;
    end;
  end;
  if not Result then
    fFilename := '';
end;

end.
Gruß, Renate

jaenicke 13. Nov 2020 07:44

AW: Versuch: TFileBrowser für Android, geht das ohne globale Variable?
 
Ich empfinde das with als deutlich schlimmer als die globale Variable. ;-)

Da du um eine Instanz ja wohl nicht herumkommst, kannst du die Referenz aber als
Delphi-Quellcode:
class var
in der Klasse unter private deklarieren. Dann hängt die nicht lose als globale Variable herum. Und zur Freigabe der Instanz gibt es einen
Delphi-Quellcode:
class destructor
. Dann musst du die Instanz vermutlich gar nicht immer neu erstellen.

Rollo62 13. Nov 2020 07:50

AW: Versuch: TFileBrowser für Android, geht das ohne globale Variable?
 
Wenn das eine globale Variable sein kann, dann könntest Du im TFileBrowser eine class var anlegen.
Normalerweise wird sowas über z.B. Instance gemacht.

Nur mal so grob hingeschrieben, ohne Gewähr:
Delphi-Quellcode:

type
  TMyClass = class
      class var FInstance;

      class function Instance : TMyClass;

      class constructor Create;
      class destructor Destroy;

      procedure CallWhatever;
  end;


class constructor TMyClass.Create;
begin
    FInstance := nil;
end;

class destructor TMyClass.Destroy;
begin
    FInstance.Free;
end;

class function TMyClass.Instance : TMyClass;
begin
    if not Assigned( FInstance ) then
    begin
        FInstance := TMyClass.Create;
    end;
 
    Result := FInstance;
end;


...
...
...

    TMyClass.Instance.CallWhatever; //<== geht jederzeit, wird beim ersten Aufruf erzeugt, und lebt bis zum Programmende in einer Instanz

....

Je nachdem sollte man das aber noch threadsafe absichern, bei Bedarf.


Edit:
@jaenicke, völlig richtig mit dem destruktor, jetzt isser drin.

Renate Schaaf 13. Nov 2020 08:06

AW: Versuch: TFileBrowser für Android, geht das ohne globale Variable?
 
Vielen Dank an euch beide, so wirds gemacht. Mal wieder dazugelernt.

Also, ohne das "with" finde ich, dass es noch doofer aussieht :)

Gruß, Renate

haentschman 13. Nov 2020 08:11

AW: Versuch: TFileBrowser für Android, geht das ohne globale Variable?
 
Zitat:

Also, ohne das "with" finde ich, dass es noch doofer aussieht
Das hat nichts mit Optik zu tun, sondern mit dem Debugging. :zwinker:

jaenicke 13. Nov 2020 08:36

AW: Versuch: TFileBrowser für Android, geht das ohne globale Variable?
 
Zitat:

Zitat von Rollo62 (Beitrag 1477242)
Wenn das eine globale Variable sein kann, dann könntest Du im TFileBrowser eine class var anlegen.
Normalerweise wird sowas über z.B. Instance gemacht.

Nur mal so grob hingeschrieben, ohne Gewähr:

Da fehlt noch der Klassendestruktor zur Freigabe (das mag hier nicht so wichtig sein, aber wenn man es sich nicht angewöhnt Speicherlecks zu vermeiden findet man im Fehlerfall, wenn es wichtig gewesen wäre, dann die Ursache nur schlecht). Initialisiert wird die Variable wiederum automatisch.

Zitat:

Zitat von haentschman (Beitrag 1477245)
Das hat nichts mit Optik zu tun, sondern mit dem Debugging. :zwinker:

Zum nicht funktionierenden Debugging kommen noch potentielle Problemen bei Updates zu neuen Delphiversionen. Ein einfaches Beispiel dafür waren damals bei ich glaube XE2 die VirtualTrees. Eigentlich hätten die normal weiter funktioniert. TRect hatte damals aber Width und Height oder so bekommen und plötzlich bog das with <TRect-Variable> den Zugriff auf Width und Height der Komponente auf das TRect um...

So etwas ist dann teilweise sehr schwer zu finden, da es oft problemlos weiter kompiliert, an anderer Stelle aber plötzlich Fehler auftreten, die man dann ewig sucht.

Von daher macht man sich damit nur unnötigerweise Probleme.

Renate Schaaf 13. Nov 2020 09:04

AW: Versuch: TFileBrowser für Android, geht das ohne globale Variable?
 
So funktioniert's prima, und ganz ohne with:

Delphi-Quellcode:
class function TFileBrowser.Instance: TFileBrowser;
begin
  if not assigned(FInstance) then
    FInstance := TFileBrowser.Create;
  Result := FInstance;
end;

class constructor TFileBrowser.Create;
begin
  //Ist doch eigentlich unnötig, oder?
  FInstance := nil;
end;

class destructor TFileBrowser.Destroy;
begin
  fInstance.free;
end;

class procedure TFileBrowser.BrowseForFile(Content: TContentEnum;
  OnExecute: TFileBrowseEvent);
begin
  Instance.Initialize(Content, OnExecute);
end;

procedure TFileBrowser.Initialize(Content: TContentEnum;
  OnExecute: TFileBrowseEvent);
var
  Intent: JIntent;
begin
  fOnExecute := OnExecute;
  fMessageSubscriptionID := TMessageManager.DefaultManager.SubscribeToMessage
    (TMessageResultNotification, HandleActivityMessage);
  Intent := TJIntent.Create;
  Intent.setType(StringToJString(ContentStrings[Content]));
  Intent.setAction(TJIntent.JavaClass.ACTION_GET_CONTENT);
  MainActivity.startActivityForResult(Intent, 0);
end;
Danke nochmal.


Alle Zeitangaben in WEZ +1. Es ist jetzt 14:48 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz