Incus J
-
Content Count
157 -
Joined
-
Last visited
Posts posted by Incus J
-
-
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.
-
Maybe - I'm not sure how though (it's a TTabControl rather than a TPageControl - so there's no icon index property for each tab).
-
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?
-
Thanks Dave and Kas - some really neat approaches there - I will give them a try!
-
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).
-
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!
-
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:
QuoteEnforcing 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.
-
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.
-
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).
-
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)
-
I think I've got it working by installing the Evergreen-Standalone version of WebView2. So it could simply be that the FMX TWebBrowser doesn't support the Fixed-Version of WebView2 currently.
-
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?
-
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?
-
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.
-
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.
-
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.)
-
FMX.Dialogs function SelectDirectory(const Caption: string; const Root: string; var Directory: string): Boolean;
Thank you f.m - that's exactly what I'm looking for!
-
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?
-
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.
-
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.
-
Thank you Stefan - I'll take a look at that too - looks like a different approach. Seems quite complex at a glance.
-
Thank you Remy - I'll give that a try!
From what you've said, I'm guessing it is not going to be possible to make a 'very general' CreateAnObjectByClassName function, because of the necessity to hard code the TMyClassType cast?
-
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; ?
-
On 12/19/2020 at 12:53 AM, Doug Rudd said:In the deployment manager, if you put ".\assets\internal\" as the Remote Path, on start of your app it copies those files to a directory (if they dont already exist) that you can get with:
tpath.GetDocumentsPath
That's very useful - thank you!
How to change the icon of the active tab in TTabControl?
in VCL
Posted
A local class helper did the trick - perfect - thank you!
Now I just call UpdateTabImages in my OnChange event handler.