I am working on a multiprocess app, where each document is opened in a new process. It behaves like Google Chrome.
Everything is working fine, except this:
When any control in the embedded app is active, the main window (which contains the embedded windows) doesn't receive keystrokes (Such as Ctrl + N, and the like). I understand the fact, that the embedded window runs in a different process, and this is why it doesn't work.
What i need to do:
- Main window should detect keystrokes pressed in the embedded window
- If main window has something assigned to the keystroke, the main window should receive the keyboard shortcut instead of the embedded form.
Some important details:
- I have control over the source code of both processes (It's the same exe, with different behaviors)
- I'm using actions for keystrokes
- Update: I use WM_COPYDATA for IPC (Sending a record through)
- Update: There is only one UI process (aka main window)
The embed code :
This code embeds the other processes' windows into the main UI window. It is called directly by the WM_COPYDATA handler, based on the received values.
procedure TWTextAppFrame.adoptChild(WindowHandle: Cardinal; Container: TWinControl);
var
WindowStyle : Integer;
FAppThreadID: Cardinal;
ts : TTabSheet;
begin
/// Set running app window styles.
WindowStyle := GetWindowLong(WindowHandle, GWL_STYLE);
WindowStyle := (WindowStyle and not WS_POPUP) or WS_CHILD;
SetWindowLong(WindowHandle, GWL_STYLE, WindowStyle);
/// Attach container app input thread to the running app input thread, so that
/// the running app receives user input.
FAppThreadID := GetWindowThreadProcessId(WindowHandle, nil);
AttachThreadInput(GetCurrentThreadId, FAppThreadId, True);
/// Changing parent of the running app to our provided container control
/// The window will be embedded on a tabsheet
ts := TTabSheet.Create(Self);
ts.TabVisible := false;
ts.PageControl := WindowPages;
ts.Tag := WindowHandle;
Winapi.Windows.SetParent(WindowHandle,ts.Handle);
SendMessage(Container.Handle, WM_UPDATEUISTATE, UIS_INITIALIZE, 0);
UpdateWindow(WindowHandle);
/// This prevents the parent control to redraw on the area of its child windows (the running app)
SetWindowLong(Container.Handle, GWL_STYLE, GetWindowLong(Container.Handle,GWL_STYLE) or WS_CLIPCHILDREN);
/// Make the running app to fill all the client area of the container
SetWindowPos(
WindowHandle,
0,
0,
0,
Container.ClientWidth,
Container.
ClientHeight,SWP_NOZORDER
);
/// This creates a small object which holds some info about the embedded window
/// (Document dirty status, embedded window handle, file name, etc. )
/// The main window uses this to identify an embedded window, and also for tracking the processes
AddHandleToControlList(WindowHandle,PChar('win'+IntToStr(WindowHandle)), 0, 0, 0, 0, alClient,ts);
/// Page control holding all the tabsheet
WindowPages.ActivePage := ts;
/// activate the embedded window
SetForegroundWindow(WindowHandle);
end;
This is how the child processes are notifying the main window:
/// Record sent through
TSendData = Packed Record
sender: Cardinal; /// The handle of the sender window
s : WideString; /// String data
i : Integer; /// Integer data, usually and identifier for a function (defined with constants)
i2 : Integer; /// Status, or other integer data
b : boolean; /// boolean information
End;
var WData: TSendData;
///...
WData.sender := Self.Handle;
WData.i := WE_SETPARENT;
/// SendCommand is a wrapper function around the WM_COPYDATA sending code
SendCommand(Self.Handle, MainWindow, WData);
What i have so far?
I use WM_COPYDATA for IPC.
No code yet, only an idea, of transmitting a list of the hotkeys to the embedded form, and register them as actions there. When the action is executed in the embedded form, the keystroke would be sent to the main form.
This idea isn't an elegant solution, i would like to know, if there is a better one, and if there is, what would that be.