The main program we are working on is completly designed with the VCL, quite large and partly about 20 years old. In the next version it shall get a more modern UI under consideration of UWP guidelines and touch control.
Since we already started with app developement, we thought about building a general framework with FMX which can be used on different platforms. In the Windows version we want to integrate most of the VCL Forms, because reimplementing them all is not worthwile for us. Also the Windows version will have quite more functionality and support for our older products. So we only want to redesign those parts of the app, which also shall be part of the mobile version.
First we thought about splitting the app into two seperate executables for Windows. But in our new UI design we don't want to use modal dialogs.
So we would like to use a FMX container into which VCL Forms could be embedded.
What I've done
In my project, I have created a FMX multi-device app with a main Form and a DLL which contains a VCL form. The DLL exports functions to create and destroy the VCL Form, and a function which returns the handle of the created VCL Form.
In the main Form of the FMX app, the DLL is loaded and the VCL form is created. After getting the handle of the VCL Form, it is embedded into the main Form by calling the SetParent() function of the Win32 API:
HWND vclFormHandle = GetVclFormHandleFromDll();
HWND fmxFormHandle = WindowHandleToPlatform(Handle)->Wnd;
SetParent(vclFormHandle, fmxFormHandle);
Questions
The embedded VCL Form can be controlled with the mouse. Also, editing text with the keyboard is possible. But switching between controls via the Tab key, or executing a Button click via the Return key, doesn't work. To get this working in the VCL Form, I added a hook like this:
HHOOK hKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyBoardProc, HInstance, NULL);
RESULT CALLBACK KeyBoardProc (int nCode, WPARAM wParam, LPARAM lParam)
{
// key event -> vcl controls
return CallNextHookEx(hKeyBoardHoof, nCode, wParam, lParam);
}
Before digging deeper into this, I wanted to ask if someone has a better idea to get those key events forwarded? (When setting a VCL Form as a parent, using the same mechanisms, it works fine without doing extra stuff).
I know that mixing VCL and FMX is not recommended by Embarcadero, but it seems as the described design could work. Can anyone see dangers or pitfalls in doing this?
Related
I have an issue in Delphi 10.1 Berlin using VCL Styles and an external DLL.
I'm using a Nitgen DLL to enroll fingerprints in my application. The process is handled by a COM object in NBSPCOM.dll.
When I call the method to enroll, the form from the DLL appears odd. It shows the Form background, images and controls are wrong, etc.
If I compile the application in XE8, the problem vanishes and all works fine.
I can't find the class of the DLL forms to try a VCL Styles hook.
Some screenshots of the form:
Original form
Wrong Form
When you uses the VCL Styles the native winapi controls (Static, Button, RebarWindow32, ...) are styled using a WH_CBT Hook, My guess is which the dll is using a static or Button winapi control where the background of the image is drawn. To overcome this you can disable the styling of these controls using the Vcl.Themes.TStyleManager.SystemHooks property like so
TStyleManager.SystemHooks := [];
Even though it is not officially supported by Embarcadero there are many examples showing that you can include a FMX form in a VCL application e.g. MonkeyMixer and this SO question.
However, when I create a test application with only one empty VCL form and one empty FMX form, I get two problems:
There are two application icons in the task bar (apparently one for
each instance of TApplication i.e. VCL and FMX)
It crashes when I close the application (when it calls TStyleManager.UnInitialize in FMX.Forms.FinalizeForms).
How can I make this work?
I need this combination as we want our application to be native on Windows, Mac OS and iOS. Therefore on Windows it is a VCL application and the other OS's are FMX using the TMS native components. We have some large custom graphical components that are made for FMX, and they must also work on Windows.
Edit:
I see only two alternative solutions, and I like none of them:
Use FMX on Windows too. I don't like the idea of styled components instead of native. Experienced users can easily tell the difference.
Maintain to sets of our custom components: VCL and FMX editions. That will require some work, and also the graphical features of FMX are much better than VCL.
I've needed to host an FMX app inside a VCL app for display and training purposes. The FMX app is really an Android target and the VCL a Windows 'demonstrator'. The FMX hosting is done using TFireMonkeyContainer hosting the FMX main form. Yes it's got slight wrinkles but it works ok and I'm sure we'll find a way to improve things.
My FMX main form is created at runtime and has visibility of only FMX.Forms. It is then passed to TFireMonkeyContainer and is destroyed by it when the VCL app closes.
I am using Delphi XE6 and VCL styles. I have main application and dlls. My main application has enabled runtime themes and I am using vcl style files. I did quite similar thing to my DLLs. I enabled runtime themes and added VCL.Themes, VCL.Styles under uses and resource file with VCL style file within it. When DLL is loaded I load VCL style from resources and set it for DLL gui. Main app and DLL are not built with runtime packages.
Now I have main app GUI styled with own style and DLL gui styled with own style too. This seems to work fine until...
When I click on button in my main app which event opens TPopupMenu it's styled with same style as DLL GUI instead of main app style. If I navigate through menu I get AV too and program crashes. Take a look at the attached image.
What am I doing wrong? The only workaround I currently see would be to make my own customized TPopupMenu derived from some other control.
As I promised I prepared simple demo program which is similar to my application. It consists of host application with own style and DLL with style added to resource. Run it and click on button Popup then try select something from popup. It will crash and stop in some StdWindowProc or something like that. Also if you go to window system menu (left top corner) when you try to select something from that menu you will notice that system menu is styled as DLL gui and crashes too. Link to rar file: dropbox.com/sh/f2jmbsmw18akpyg/AAA6SWdBmVhf6n6K-mvYLLmua?dl=0
Thanks for your help.
This is a fundamental problem with VCL styles and the way that they style menus. The styling is implemented with a process wide hook. Specifically a CBT hook installed by a call to SetWindowsHookEx from TCustomStyleEngine.CreateSysHook in the Vcl.Themes unit. In fact, the hook applies just to the GUI thread, but that is process wide in the sense that there is exactly one GUI thread in the process.
Since you have multiple instances of the VCL in your application (one in the DLL and one in the application), two hooks are installed. That is one too many. The hook installed most recently (the DLL as it happens) wins, and that's why the DLL menu styling infects your executable. And why you encounter an access violation. The DLL is trying to operate on a menu that belongs to the executable. And so, in spite of your best efforts, you've ended up with the DLL code accessing VCL objects from the host executable.
There's no simple way to work around this and support styles fully in both modules. What we have here is a fundamental consequence of the design. The system was not designed to support multiple VCL instances. If you wish to use VCL styles in multiple modules, then the designers expect you to use runtime packages.
I suppose that you might be able to get some traction by operating the DLL out of a completely different thread. That would involve loading the DLL from that different thread so that the VCL is initialized in the thread. And all calls to the DLL would have to be from that thread. And you'd need to run a message loop in that thread. It's possible that you might be able to make that work, but I doubt it. Even with all the provisos mentioned you still have to handle the fact that you have two GUI threads which presents all sorts of issues with the input queue handling.
Perhaps another approach would be to uninstall the hook from the DLL. So long as your DLL is not showing menus then you may well be able to get away with uninstalling that hook. It would disable styling for menus shown by the DLL, but perhaps that's acceptable.
This version of your DLL (after I simplified it somewhat also) uninstalls the hook.
library VCLStyleDLL;
{$R 'Style.res' 'Style.rc'}
uses
VCL.Styles,
VCL.Themes,
VCL.SysStyles; // to gain access to TSysPopupStyleHook
{$R *.res}
begin
TStyleManager.TrySetStyle('Glossy', false);
TCustomStyleEngine.UnRegisterSysStyleHook('#32768', TSysPopupStyleHook);
end.
With this version of the DLL, the host executable does not suffer the problems your describe in your question.
As David says this is caused because each VCL instance install a hook to detect when a popup menu (#32768) is created. So there is two hook instances working at the same time.
As workaround you can disable the popupmenu style hook in the dll (or in the app) using the UnRegisterSysStyleHook function defined in the Vcl.SysStyles unit.
TCustomStyleEngine.UnRegisterSysStyleHook('#32768', TSysPopupStyleHook);
I am using Delphi XE6 and VCL styles. I have main application and dlls. My main application has enabled runtime themes and I am using vcl style files. I did quite similar thing to my DLLs. I enabled runtime themes and added VCL.Themes, VCL.Styles under uses and resource file with VCL style file within it. When DLL is loaded I load VCL style from resources and set it for DLL gui. Main app and DLL are not built with runtime packages.
Now I have main app GUI styled with own style and DLL gui styled with own style too. This seems to work fine until...
When I click on button in my main app which event opens TPopupMenu it's styled with same style as DLL GUI instead of main app style. If I navigate through menu I get AV too and program crashes. Take a look at the attached image.
What am I doing wrong? The only workaround I currently see would be to make my own customized TPopupMenu derived from some other control.
As I promised I prepared simple demo program which is similar to my application. It consists of host application with own style and DLL with style added to resource. Run it and click on button Popup then try select something from popup. It will crash and stop in some StdWindowProc or something like that. Also if you go to window system menu (left top corner) when you try to select something from that menu you will notice that system menu is styled as DLL gui and crashes too. Link to rar file: dropbox.com/sh/f2jmbsmw18akpyg/AAA6SWdBmVhf6n6K-mvYLLmua?dl=0
Thanks for your help.
This is a fundamental problem with VCL styles and the way that they style menus. The styling is implemented with a process wide hook. Specifically a CBT hook installed by a call to SetWindowsHookEx from TCustomStyleEngine.CreateSysHook in the Vcl.Themes unit. In fact, the hook applies just to the GUI thread, but that is process wide in the sense that there is exactly one GUI thread in the process.
Since you have multiple instances of the VCL in your application (one in the DLL and one in the application), two hooks are installed. That is one too many. The hook installed most recently (the DLL as it happens) wins, and that's why the DLL menu styling infects your executable. And why you encounter an access violation. The DLL is trying to operate on a menu that belongs to the executable. And so, in spite of your best efforts, you've ended up with the DLL code accessing VCL objects from the host executable.
There's no simple way to work around this and support styles fully in both modules. What we have here is a fundamental consequence of the design. The system was not designed to support multiple VCL instances. If you wish to use VCL styles in multiple modules, then the designers expect you to use runtime packages.
I suppose that you might be able to get some traction by operating the DLL out of a completely different thread. That would involve loading the DLL from that different thread so that the VCL is initialized in the thread. And all calls to the DLL would have to be from that thread. And you'd need to run a message loop in that thread. It's possible that you might be able to make that work, but I doubt it. Even with all the provisos mentioned you still have to handle the fact that you have two GUI threads which presents all sorts of issues with the input queue handling.
Perhaps another approach would be to uninstall the hook from the DLL. So long as your DLL is not showing menus then you may well be able to get away with uninstalling that hook. It would disable styling for menus shown by the DLL, but perhaps that's acceptable.
This version of your DLL (after I simplified it somewhat also) uninstalls the hook.
library VCLStyleDLL;
{$R 'Style.res' 'Style.rc'}
uses
VCL.Styles,
VCL.Themes,
VCL.SysStyles; // to gain access to TSysPopupStyleHook
{$R *.res}
begin
TStyleManager.TrySetStyle('Glossy', false);
TCustomStyleEngine.UnRegisterSysStyleHook('#32768', TSysPopupStyleHook);
end.
With this version of the DLL, the host executable does not suffer the problems your describe in your question.
As David says this is caused because each VCL instance install a hook to detect when a popup menu (#32768) is created. So there is two hook instances working at the same time.
As workaround you can disable the popupmenu style hook in the dll (or in the app) using the UnRegisterSysStyleHook function defined in the Vcl.SysStyles unit.
TCustomStyleEngine.UnRegisterSysStyleHook('#32768', TSysPopupStyleHook);
Is there a way to create an MDI child window from an ActiveX dll written in Delphi 5, and to embed it in a MDI parent window created from a Delphi XE windows client application? If not, is there a way to mimic the behavior?
Background
There is an application written entirely in Delphi 5. The main form of the application is an MDI parent window. All other forms in the application are MDI child forms, and every one of them is created from an ActiveX library. The parent application creates the ActiveX, after which is calls a method of the ActiveX interface. From this method a form is created and it's FormStyle is set to fsMDIChild. At this point the form is an MDI child of the MDI parent. This works because both the application and the ActiveX libraries are compiled using runtime packages. As a result, all forms share the same instance of TApplication.
The Problem
The application is very large, and needs to be migrated to Delphi 2010 or Delphi XE. It would be fantastic if the application could be migrated systematically, by first migrating the application, and then migrating the ActiveX libraries one at a time (there are about 50 of them).
The problem is that if the console application is compiled in XE, it will no longer be using the same TApplication instance as those libraries still compiled in Delphi 5.
Even if the forms in the ActiveX library cannot be true MDI child windows, it seems like I should be able to return the handle of the form that is created from the ActiveX and grab it from the main form and make the form appear to be an MDI child. I could then create my own layer for handling events.
Any ideas?
Update: The approach currently being taken with this application is that it is being migrated from MDI to an SDI interface. It is perfectly possible to instantiate TForms from a Delphi 5 ActiveX DLL from a Delphi XE application, so long as the first form from each DLL can take care of it's own data (loading, saving, displaying additional forms, etc). The problem was in keeping the original MDI design. If the SDI design proves acceptable, there will be no need for an MDI solution. Still, if someone knows how to accomplish the MDI solution, I'd like to know.
Originally, I said that you cannot create do so.
I researched some more and found out that it's possible to do it.
You have to be very careful though.
Here's some source I created recently to test out the idea: http://cc.embarcadero.com/item/28168
The code spawns the Windows Calculator and Notepad app, then MDIize the external windows into the MDI Form.
Click Launch Notepad after starting the app, and see what happens.
You should be able to modify the work further so that you can achieve what you need.
Note that you need to ensure that your MDI Child in the ActiveX DLL is entirely self-contained.
Even if the forms in the ActiveX
library cannot be true MDI child
windows, it seems like I should be
able to return the handle of the form
that is created from the ActiveX and
grab it from the main form and make
the form appear to be an MDI child. I
could then create my own layer for
handling events.
I'd try something like this (inspired by Marjan's comment):
in the Delphi 5 MDI windows, split the Window in two layers for each of forms:
a set of frameless TForms/TFrames having the content (maybe expose this as an ActiveX forms)
for each frameless content, one MDI child that handles the MDI
in the Delphi XE host:
obtain the handle for each of the Delphi 5 frameless TForms/TFrames
embed that handle in an MDI child form
It probably means you have to duplicate part of the Delphi 5 MDI handling in Delphi XE.
--jeroen