Jump to content

Incus J

Members
  • Content Count

    157
  • Joined

  • Last visited

Posts posted by Incus J


  1. Quote

      4 hours ago, Incus J said:

    whereas the second version would end up returning (a pointer to?) an instance of MyObject that had just been freed

    No.  It would not return anything at all, because the parameter is being passed in by value, not by var reference.

    Ah - so that is a key difference to, for example, passing in an parameter of type TStringlist - even without 'var' I think an object such as TStringlist gets passed by reference (?)

     

     


  2. A global setting sounds promising.  

    FMX.WebBrowser.Win.TGlobalEdgeBrowserSettings.UserDataFolder:='(my path)';

    That's giving me an error of Undeclared identifier TGlobalEdgeBrowserSettings at the moment.  That class doesn't seem to be present in the FMX.WebBrowser.Win unit.  I'll have a look around for it elsewhere - maybe it's moved in 11.3?


  3. So much to learn - and so many opportunities to make mistakes that will still compile while unwittingly shooting myself in the foot 🙂

     

    I didn't even know an untyped var parameter was permitted - in my specific case it would be typed, as below, but I can see myself doing something like accidentally specifying a type of TMyInterface instead of IMyInterface due to a momentary distraction, tiredness or loss of focus.

    Quote

    Specifying var when passing a variable to a function parameter is not valid syntax

    Yes, sorry - that's a typo on my part, should have been:

    if SomeclassInstance.CreateAndReturnAnObject(MyObject) then begin

    My understanding now is that the first version of the declaration below (with var) would successfully auto-reference count and retain MyObject on return, whereas the second version would end up returning (a pointer to?) an instance of MyObject that had just been freed:

    function TSomeclass.CreateAndReturnAnObject(var MyObject:IMyInterface):boolean;
    begin
      MyObject:=TMyInterface.Create;
      result:=true;
    end; //MyObject retained.
    
    vs
    
    function TSomeclass.CreateAndReturnAnObject(MyObject:IMyInterface):boolean;
    begin
      MyObject:=TMyInterface.Create;
      result:=true;
    end; //MyObject vapourises here.

    When an interfaced object (e.g. MyObject) goes out of scope and is automatically freed, does it get nil assigned to it, or does it behave the same as non-interfaced objects which keep a dangling pointer to the freed instance after SomeObject.Free (unless SomeObject.FreeAndNil is used to free it)?


  4. Thank you - that's a good clear explanation.  I'll have a go at your suggestion and step through with the debugger to see what actually happens.

    Quote

    IMO it would be better if you declare CreateAndReturnAnObject a function that returns an interface.

    That does make sense, though I like to have a boolean return value to test success against in a compact way:

    if TSomeclass.CreateAndReturnAnObject(var MyObject) then begin
    
    vs
    
    MyObject := TSomeclass.CreateAndReturnAnObject;
    if Assigned(MyObject) then begin

    ...but perhaps there's a better approach?  


  5. I'm reading up on interfaces, and trying to understand when a reference counted object will be freed.

    var
      MyObject:IMyInterface;
    begin
      if TSomeclass.CreateAndReturnAnObject(var MyObject) then begin
        //Do something with MyObject
      end;
    end; //MyObject is freed automatically here?

    If CreateAndReturnAnObject creates an object of type TMyInterface (which implements IMyInterface) and returns it via the MyObject parameter, will the object still exist immediately after CreateAndReturnAnObject returns - or will it have fallen out of scope?  How about if I omit 'var' from the parameter?

     

    Since MyObject starts of as nil, I'm guessing it can't have its reference count auto-incremented until after it is created, so will it cease to exist once CreateAndReturnAnObject completes, in which case I can't 'Do something with MyObject', or does the compiler know to keep it around somehow?


  6. Quote

    This property defaults to a local app data folder

    Yes, with VCL TWebBrowser EdgeUserDataFolder defaults to a path beginning with %LOCALAPPDATA%...etc... and this can be customised.

     

    Unfortunately with FMX TWebBrowser the default seems to be the folder of the app executable instead.

     

    So I'm trying to find a way to surface and set the underlying UserDataFolder property (defined in TEdgeBrowserHelper) for an FMX TWebBrowser.  That way I can:

    • Set the FMX TWebBrowser engine to IEOnly at design time
    • At runtime my app can set Edge UserDataFolder to my new desired (writable) path - and only then:
    • Switch the engine to EdgeOnly so it will initialize WebView2 with my new path.
    Quote

    for that you'll need re-CALL it as above...

    I'm not sure how to (re-)call it.  Note that as things initially stand it will not have been called successfully yet.


  7. If I subclass FMX.TWebBrowser e.g. TWebBrowserPlus=class(TWebBrowser) can I surface and access Private member FWeb ?  I thought perhaps I could simply redeclare FWeb like this:

    type TWebBrowserPlus=class(TWebBrowser)
      private
        FWeb: ICustomBrowser; //redeclare private ancestor field to surface it in my subclass...?

    ...but that doesn't seem to work.


  8. That would make sense.  So far I've navigated:

     TWinNativeWebBrowser.CreateHandle calls 

     TWinNativeWebBrowser.CreateWebView which calls

     TWinNativeWebBrowser.InitializeWebView which calls 

     CreateCoreWebView2EnvironmentWithOptions

     

    I've yet to find the bit where FMX.TWebBrowser calls TWinNativeWebBrowser.CreateHandle - but there must be a connection between those two somewhere.

     

    I've noticed there's a TEdgeBrowserHelper = class helper for TWinNativeWebBrowser which has:

    property UserDataFolder: string read GetUserDataFolder write SetUserDataFolder;

    ...that's the property I'm after I think.

     

    Now I need to figure out how to access that property from my FMX application, when all I have is my instance of FMX.TWebBrowser on a form.  Could I do something like TMyWebBrowser=class(TWebBrowser) and then cast TMyWebBrowser(WebBrowser1) to get at its internals?


  9. TWebBrowser and TEdgeBrowser VCL components have a property named UserDataFolder/EdgeUserDataFolder which sets a folder for the Edge engine (WebView2) to write session data.

     

    TWebBrowser FMX component does not expose this property, and the component defaults to using the app executable folder to write session data.  That default value is problematic because the Program Files folder where the app is installed to is not writable.  So I need to set a different location.

     

    I know the UserDataFolder property is in there somewhere, so I'm hopeful that I can surface it somehow.  But I'm having trouble navigating through the FMX.WebBrowser code since it branches and calls services depending on the platform and engine - FindDeclaration only gets me so far.

     

    How can I access and set the Edge UserDataFolder when using the FMX TWebBrowser component?  


  10. Thank you for the suggestions and code example - I will have an explore.

     

    Yes I agree TStringlist is slightly heavy for the job - though I do find it useful when stepping through code in the debugger as there is a visualiser to display the contents.  I started out with a dynamic array, but got confused trying to pass the array into and out of subroutines, and the TArray type seemed to lack useful methods such as myArray.IndexOf ...basically my lack of experience with arrays led me to switch to TStringlist to save time!

     

    My main reason for rolling my own is that I've discovered the built in StrToDate function differs in behaviour between Delphi versions - so I want to create my own function that I can rely on to maintain the same behaviour going forward. 


  11. Yes - at the moment I have a private InitialParse subroutine that basically attempts to grab three date components from whatever string it is fed, and drops them into a TStringlist (e.g. from 'Feb 7th, ''23' it will extract 'Feb' and '7th' and '23').

    What to do with those three components (how to interpret them) is decided later.  It works, but if necessary this subroutine could be made much stricter in terms of what it will accept as input.

    class procedure TDateUtils.InitialParse(s: string; var ts:TStringlist);
    {Pull three components from the date string.}
    var
      i:integer;
      c:char;
      bSkip:boolean;
      sPart:string;
    begin
      sPart:='';
      bSkip:=true;
      for i:=1 to Length(s) do begin
        c:=s[i];
        if c.IsLetterOrDigit then begin
          if bSkip then begin
            bSkip:=false;
            AddPart(ts,sPart);
            end;
          sPart:=sPart+c;
          end
        else bSkip:=true;
        end;
      AddPart(ts,sPart);
      while ts.Count<3 do ts.Add('0'); //Ensure 3 date components.  
      end;
    
    class procedure TDateUtils.AddPart(var ts:TStringlist; var s:string);
    {Add a complete component e.g. '7th' to the list.}
    begin
      if s<>'' then begin
        if ts.Count<3 then ts.Add(s);
        s:=''; //Reset.
        end;
      end;

    It could also handle the 'more than three components present' situation more efficiently.

     


  12. The IDE copied a PAServer package into the scratch-dir folder for me, which I then installed - so yes it likely is the one from the Delphi program files folder.  

     

    I'm currently downloading a version of Xcode that I think includes Python 3.8 and still supports Catalina.

     

    Thank you for the link - I'll try the package you suggested from the website, as that might be a quick fix - before I resort to trying to update Xcode.


  13. When I try to run (deploy) an FMX macOS app from Alexandria 11.2 I get the following error message:

     

    "Unable to start LLDB kernel: 'Symbolic link target does not exist: /Applications/PAServer-22.0.app ... Xcode.app ... Versions/3.8/Python3.

    Please make sure that the target exists or update the link to point to the new location of the Python 3 installation'.

     

    On the Windows side I have Alexandria 11.2 on a Windows 10 VM (VMWare Fusion 11.5.6).

    On the Mac side I have PAServer22 Version 13.2.11.7 on macOS Catalina 10.15.7

     

    Pretty sure I had this working in an earlier release of Alexandria(?).  Everything runs fine with Delphi 10.4.1 and PAServer21 on the same system.

    I'm guessing I need Python 3, or if that's already present, some way to tell PAServer where Python 3 is located?

     

    Edit: A look inside the package contents of Xcode.app reveals Python 3 is located in ... Frameworks/Python3.framework/Versions/3.7/Python3

    So 3.7 vs 3.8 - I wonder whether it actually requires 3.8 or whether there's a way to point it to 3.7 ?

     


  14. I've created a general purpose string to date function that is quite tolerant and can parse a variety of date strings using just a couple of parameters like this:

    'February 7th, ''23', TDateOrder.doMDY

    Its current tolerance for variations (e.g. 'Feb 7 2023' '02/07/23' "7th.02 '23") risks it getting creative if fed a string that isn't really a date, but I can tighten it up later if it needs to be stricter (famous last words - what could possibly go wrong).


  15. I agree FMX tends to change more between versions, and it can be a challenge to keep up.  But to clarify (hopefully) - my development system is HiDPI and I find when I open any existing VCL form in Alexandria it modifies the position and dimension values (the actual stored x/y/width/height) of all objects on the form.  The forms then no longer work on older versions of Windows (e.g. 7/8) - so I avoid Alexandria for VCL projects.

     

    Don't get me wrong - I love the look of the HiDPI IDE in Alexandria, and FMX forms behave fine in it, but feel I can not use it for VCL projects.  I may have strayed off topic.

    • Like 1

  16. Thank you for all the responses.  I'm in 10.4.1 and so far can't get either mmm nor MMM to parse the months - but this is an FMX project, so I can probably risk taking it into 11 Alexandria.

     

    My general take away from this is that the behaviour of Delphi's built in StrToDate function differs between versions, and might be buggy.  I think I'm going to roll my own StrToDate function, since ideally I'd like to parse dates containing characters like commas too, such as "Feb 7, 2023".

     

    Interestingly I found that VarToDateTime (variant to date time) parses dates with commas OK on Windows, but sadly not on macOS - so I'm guessing the OS provides the conversion for that function.


  17. var
      fmt:TFormatSettings;
      dt:TDate;
    begin
      fmt:=TFormatSettings.Create('en-GB');
      fmt.DateSeparator:='/';
      fmt.ShortDateFormat:='mmm/d/yyyy';
      dt:=StrToDate('Feb/7/2023',fmt);
    end;

    I'm having trouble converting dates in a string to TDate.  The above test code raises an EConvertError - 'Feb/7/2023' is not a valid date.  Where am I going wrong?

     


  18. Thanks for the reply.  

    On 10/11/2022 at 8:13 PM, Brandon Staggs said:

    IMO, the modern alternative is a decent website.

    Yes - even in the hey day of PAD files and download sites I felt a good website was a requisite.

     

    I did feel many download sites flushed away their reputation over night when they began wrapping programs in dubious installers and bundles, without permission.  I ended up adding a clause to my license agreement to the effect of "you can distribute this so long as you don't wrap it in anything".

     

    I still think a browsable library of software is useful when looking for software to do task x.  More focussed than search engines, which can seem fickle in what they surface.  A small developer may have a little gem of a program, but not necessarily SEO too.  With a library there's usually a few contenders to try out.  I guess that's App stores now!

     

     


  19. I’m perhaps a bit late with this observation, but notice that the Association of Software Professionals organisation (formerly Association of Shareware Professionals) was dissolved at the end of 2021 - and the PAD 4.0 file specification released to the public domain in January 2022.

     

    I was never a member of the ASP, but used PAD 3.11 files to publish information about my software online for years.  Kind of parted company when PAD 4.0 migrated everything behind closed doors.

     

    I still maintain PAD 3.11 files for some software, in case any download sites still poll them for updates.

     

    Just wondered what people’s thoughts were, and what any modern alternative to PAD files might be these days?


  20. Thank you Remy, thank you David.  Your test app description confirms my sinking feeling - TDirectory GetDirectories & GetFiles functions are not currently geared up to handling the nuances of the macOS file system.  They would need to become package aware at the very least - perhaps something for a future Delphi release.

     

    12 hours ago, Remy Lebeau said:

    Unfortunately, the TDirectory.GetFiles() predicate is only called for files and not for directories (ie, only for items that do not have the faDirectory attribute).

     

    That in itself might be OK.  I could let GetFiles drill down into packages, but in the predicate reject each file that is on a package path - since each file passed into the predicate will include access to its path.

     

    So I would (somehow) build a TStringList of folders that are packages, in a first pass e.g.

    /Pictures/PhotosLibrary.photoslibrary

    /Pictures/Photo Booth Library

     

    Then once I've built that list, call GetFiles.  When the predicate receives /Pictures/PhotosLibrary.photoslibrary/somefile.ext I can compare its path against my list and reject it.  Not very efficient though.

     

    Any tips for using the FileManager API directly?  Never tried to call macOS/Cocoa directly from Delphi.  Does the RTL include functions to help with that?  I'm guessing converting parameter types from Cocoa to Delphi and back again might be tricky.


  21. Apple's developer documentation mentions:

    static var skipsPackageDescendants: FileManager.DirectoryEnumerationOptions
    An option to treat packages like files and not descend into their contents.

    I think that is the behaviour I'm looking for, but I'm not sure how I can achieve it with Delphi.

     

    Perhaps if I can determine which folders/paths are packages ahead of time, I could then use the Filter Predicate function to filter out all files on those paths:

    TDirectory.GetFiles(path, TSearchOption.soAllDirectories, MyFilterFunction);

     

     


  22. I'm using System.IOUtils TDirectory.GetFiles function to list files (and recurse into subfolders) on macOS.

     

    Trouble is, in addition to normal folders, it is also listing the contents of Packages.  For example, if I ask GetFiles to recursively scan the standard /Pictures folder, the internal contents of the PhotosLibrary.photoslibrary package also gets added to the list.  This sort of makes sense in a way, since on macOS a Package is essentially a special kind of folder:  In Finder I can right-click on a Package and select View Package Contents.

     

    Question is though, how can I prevent GetFiles from looking inside and listing Package contents, and just return a list of the files a user would normally see, instead?

×