Jump to content

Incus J

Members
  • Content Count

    157
  • Joined

  • Last visited

Posts posted by Incus J


  1. Well the two icons are in an ImageList assigned to the control.  OnGetImageIndex sets the desired icon like this:

    procedure TForm1.TabControl1GetImageIndex(Sender: TObject; TabIndex: Integer;
      var ImageIndex: Integer);
    begin
      if TabControl1.TabIndex=TabIndex then ImageIndex:=1 else ImageIndex:=0;
      end;

    So if the tab is currently active it gets image 1, otherwise image 0.  Which is great - it works.  But only once at start up.  I can't figure out how to assign a different icon index later.


  2. I'd like to change the icon image on a tab in a TTabControl whenever that tab becomes active.  So the icon acts a bit like a highlight for the currently selected tab.  At the moment I have an ImageList assigned to the TTabControl Images property - and this contains two icons (one for Inactive, one for Active).

     

    I can use the OnGetImageIndex event to set the appropriate image for each tab - and this works initially.  The currently active tab displays one image, and all inactive tabs display the other image.  So far so good.  Unfortunately OnGetImageIndex seems to fire once per tab only, when the TTabControl first loads up (in TTabControl.Loaded -> UpdateTabImages) - it never fires again (?) - so this mechanism doesn't allow a tab's image index to be changed later, say in response to a different tab becoming active. 

     

    Could I somehow call the protected UpdateTabImages procedure manually, in response to a different tab being clicked and becoming active?


  3. I think that's actually quite a good solution!  (Especially given what TEdgeBrowser provides for you to work with :)

     

    If I've understood it correctly it does mean that JSExec2 could complete before JSExec1 completes - if JSExec1 turns out to be the lengthier process.  But it kind of gives synchronous behaviour in the end by waiting for all results to come in.

     

    Would just need to be careful to ensure script2 doesn't depend directly on the result of script1 (?) (since they might not complete in the order they were queued).


  4. I guess I could modify things so neither class initialises at startup.  And have some separate custom startup routine that explicitly initialises each class in the order I desire.  But to make that work I'd need to give each class a non-standard class initialisation method (i.e. not called Create) for me to call.  Which seems a bit ugly.  Definitely interested to hear David's thoughts!


  5. 3 hours ago, Darian Miller said:

    You'll want to read through this thread

    Oh - yes, thank you.

     

    Some time later:  My head hurts.  This line jumped out at me though:

    Quote

    Enforcing dependencies in initialisation order is not robust. I prefer to find a different way to solve such problems. 

     

    Based on what I've just read I think I might abandon trying to get TClassA to initialise before TClassB, and find another approach where it no longer matters which order the classes get initialised in.  Yet somehow I've got to get that data storage path initialised before TClassB initialises - but since the path will be used by other classes too, I can't really do the obvious and get TClassB to initialise the path itself.


  6. Thanks - although that didn't work, it did get me thinking more closely about what is actually happening:  TClassA is a Controller that uses TClassB (Model), so B probably gets class construction priority.

     

    However although TClassB is unaware of TClassA (as it currently stands unitA uses unitB, but unitB does not use unitA) TClassA initialises a data storage path elsewhere that TClassB happens to use.  I sense a restructuring is required.


  7. ExecuteScript runs asynchronously I think - it returns (potentially) before the script has finished executing.  There's a web page View Source example here, which looks similar to your setup, but the event has different parameters:

     

    https://blogs.embarcadero.com/execute-scripts-and-view-source-with-tedgebrowser/

     

    uses
      System.NetEncoding;
    
    procedure TEdgeViewForm.EdgeBrowser1ExecuteScript(Sender: TCustomEdgeBrowser;
      AResult: HRESULT; const AResultObjectAsJson: string);
    begin
      if AResultObjectAsJson <> 'null' then
        memoHTML.Text := TNetEncoding.URL.Decode(AResultObjectAsJson).DeQuotedString('"');
    end;

    The above code looks slightly odd at a glance, in that the event is named ExecuteScript too - perhaps it's a typo?

     

    A really messy way to wait, at least just as a quick test, might be:

    procedure TForm1.Button1Click(Sender: TObject);
    var
      LMyScript: String;
    begin
      FMyResult := ''; // Clear it
      LMyScript := 'window.chrome.webview.postMessage("MyResult");';
      EdgeBrowser.ExecuteScript(LMyScript);
    
      //How wait for EdgeBrowserWebMessageReceived() to get FMyResult?
      while FMyResult = '' do Application.ProcessMessages; // Not good - but might work
    
      ShowMessage(FMyResult);
    end;

    Of course if the script fails to return a result, your app is left hanging - and Application.ProcessMessages would allow other events to trigger that you might not be anticipating - like the user clicking buttons in the UI.  Assuming synchronous behaviour is desired, you could perhaps avoid Application.ProcessMessages altogether by moving ShowMessage(FMyResult); into the WebMessageReceived handler, and disable the UI while your function executes?

     

    Incidentally I've posted a feature request for an improved FMX TWebBrowser.EvaluateJavascript function that returns a result.  I don't think FMX TWebBrowser currently surfaces an OnScriptExecutionCompleted event: https://quality.embarcadero.com/browse/RSP-35537

     

    Since getting a result back from script execution is a very common use case, I think it would be great if VCL TEdgeBrowser and FMX TWebBrowser could be enhanced to include this sort of functionality built in (synchronous and asynchronous).


  8. I have two classes (TClassA and TClassB) declared in separate units.  Both have a class constructor which executes automatically when the app starts:

     

    type TClassB=class(TObject)
      class constructor Create;
    
    ...
    
    class constructor TClassB.Create;
    begin
      //Some class initialization code
    end

    Note that this is a class constructor, not a regular instance constructor.

     

    At present, when the app starts up TClassB's class constructor runs first, and then TClassA's class constructor follows afterwards.

    In this particular case I want the opposite to occur - I want TClassA to run its class constructor code before TClassB runs its class constructor.

     

    How can I achieve that?  (I've tried swapping the order of the unit declarations in the Project source .dpr file, but that had no effect) 


  9. Thank you - I'm currently reading.

     

    Yes, I'm using Delphi's built in FMX TWebBrowser control - which has a WindowsEngine property to select the Edge engine at design time.  I just haven't figured out how to get it to work at runtime.

     

    It is interesting that the VCL TEdgeBrowser control exposes the BrowserExecutableFolder property in the Object Inspector at design time.  But FMX TWebBrowser does not.  I wonder whether that is an intended or unintended omission?


  10. I think I'm slowly beginning to get a glimmer of understanding - it's slightly more complex than I anticipated.  As well as the EdgeView2 SDK, I've also downloaded and expanded Microsoft's Fixed-Version-Runtime of WebView2, and placed the resulting folder(s) inside my app folder.  So I think I now have all the ingredients.

     

    TWebBrowser will need to know where this Fixed-Version-Runtime folder is.  How do I set a path to this in FMX TWebBrowser?  There is the BrowserExecutableFolder property which I suspect is for this purpose.  It is defined in the FMX.WebBrowser.Win unit:

    TEdgeBrowserHelper = class helper for TWinNativeWebBrowser
    ...
    property BrowserExecutableFolder: string read GetBrowserExecutableFolder write SetBrowserExecutableFolder;

    But I've yet to discover exactly how this helper attaches to TWebBrowser itself - BrowserExecutableFolder is not a property exposed in the Object Inspector.  Presumably the private field FWeb references.

     

    Do I somehow need to cast my TWebBrowser instance to a TWinNativeWebBrowser at runtime in order to set this property?  All of this seems private - the types are declared in the unit implementation section.  I must be expected to access this some other way.

     

    How do I ensure the BrowserExecutableFolder property is set to the correct path prior to TWebBrowser attempting to initialise its view?


  11. No I hadn't - now installed, thank you!

     

    After dropping WebView2Loader.dll into the app folder, the error message has changed to "Failed to create instance of Edge browser engine"

    (The DLL file is version 1.0.664.37).  That suggests the Edge engine is available though.  It is failing here:

     

    procedure TWinNativeWebBrowser.InitializeWebView;
    begin
      ...
      // Create the environment
      var hr := CreateCoreWebView2EnvironmentWithOptions(PChar(BrowserExecutableFolder), PChar(UserDataFolder), nil,
        Callback<HResult, ICoreWebView2Environment>.CreateAs<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
          CreateEnvironmentCompleted));
      if not ProcessHResult(hr) then
      begin
        FBrowserControlState := TBrowserControlState.Failed;
        DoCreateWebViewCompleted(False);
      end;
    end;

     

    BrowserExecutableFolder and UserDataFolder both appear to be empty - so I need to check whether that is significant.  

    ProcessHResult(hr) is returning False for a hr value of -2147024894.


  12. When I drag and drop new controls from the Palette onto an FMX Form, I'm finding the control tends to be created some way off the right and bottom edge of the form.  In other words the new control does not appear anywhere near the location of my mouse cursor.  I can correct it by typing coordinates into the Position property in the Object Inspector.

     

    Is anyone else experiencing this, or is it a quirk of my setup?  My system is High-DPI.


  13. I'm unsure how to get FMX TWebBrowser to use the Edge engine instead of the older IE engine.

     

    I've set its WindowsEngine property to EdgeOnly (Delphi 11).  But I'm seeing a runtime error "Edge browser engine unavailable".

     

    Edge is installed.  I think in the past a .dll file was required (?)  I'm not sure whether this is still the case.  After a Google search I looked in Delphi's Redist folder - but can't see anything that looks promising there.

     

    (On a vaguely related note - is there a way to z-order web controls in FMX?  I'd like to place some controls so that they partially overlap TWebBrowser - appear on top of the web content - but they get clipped.)


  14. Thank you Remy, Wil, f.m,

     

    The code I found online is the same fmxexpress example Wil mentions above, so I've been playing around with it.  If I comment out the following block, then it works without crashing the app:

    if ADir <> '' then begin
      LInitialDir := TNSURL.Create;
      LInitialDir.initFileURLWithPath(NSSTR(ADir));
      LOpenDir.setDirectoryURL(LInitialDir);
    end;

    The crash was occurring on LInitialDir.initFileURLWithPath(NSSTR(ADir)); and when I stepped through the code ADir appeared to be '' empty, so I'm not sure how this block was executing at all.  I guess without this block I'm losing the capability for specifying an initial starting folder.

     

    I also get a warning that NSSTR is deprecated, but the suggested replacement StrToNSStr is not recognised.  Is there an easy way to query which system unit a function might be in?


  15. Does Delphi 10.4 FMX have a built in routine or component to display a Choose a Folder dialog?  I can see dialog components to open and save a file, but not to select a folder.  If there isn't anything built in, how might I achieve this?

     

    I'm looking for something that will work on both Windows and macOS.  I have found some code online, and the Windows side of it works, but it looks fairly elaborate, and the macOS code crashes the app with Exception class 6.  So perhaps there is an easier approach using existing routines.  I'm running 10.4 Update 1 at present.


  16. TMyClassType = class of TMyClass;

    Can the metaclass type TMyClassType be passed into a function as a parameter? TMetaClass...?

    function CreateAnObjectByClassName(ClassName:string; BaseClass:TMetaClass..?):TPersistent;
    begin
      result := BaseClass(GetClass(ClassName)).Create;
    end;

    If that's possible, then I can have the caller provide the base class (the ancestor which introduced the virtual constructor) like this:

    object := CreateAnObjectByClassName('TMyClass2', TMyClassType);

    I'm not sure what type BaseClass would be - I don't think it's just a TClass.


  17. I have a class descended from TInterfacedPersistent like this:

    type TMyClass=class(TInterfacedPersistent,IMyInterface);

    …and several classes descended from this, all registered with RegisterClasses([TMyClass1, TMyClass2, …etc…]);

     

    I’d like to make a very general function to create instances of these (and other classes) at runtime, by simply passing in a string containing the class name:

    function CreateAnObjectByClassName(AClassName:string):TPersistent;
    begin
      result := GetClass(AClassName).Create;
    end;

    This kind of works, except that when creating the object, it looks like its ancestor TObject.Create constructor is called, instead of TMyClass2.Create;

    When I step through the code at runtime I end up at constructor TObject.Create; in the System unit.  As a result the newly created object is not initialised correctly.

     

    Having just the class name (e.g. 'TMyClass2') as a string, how can I create a genuine instance of that class, as if I had hard coded TMyClass2.Create; ?

×